import { Component, ViewEncapsulation, OnInit } from '@angular/core'; import { CurrencyPipe, NgClass } from '@angular/common'; import { RouterModule } from '@angular/router'; import { MatButtonModule } from '@angular/material/button'; import { MatButtonToggleModule } from '@angular/material/button-toggle'; import { MatRippleModule } from '@angular/material/core'; import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; import { MatTableModule } from '@angular/material/table'; import { MatTabsModule } from '@angular/material/tabs'; import { TranslocoModule } from '@ngneat/transloco'; import { NgApexchartsModule } from 'ng-apexcharts'; import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { CommonModule } from '@angular/common'; import { environment } from '../../../../environments/environment'; import { ApexOptions } from 'ng-apexcharts'; interface DashboardMetrics { machine_title: string; machine_count: number; clients: number; company_users: number; client_users: number; transactions: number; sales: string; active_machines?: number; inactive_machines?: number; machine_operation_status?: { [key: string]: number }; machine_stock_status?: { [key: string]: number }; payment_breakdown?: { cash: number; cashless: number; upi_wallet_card: number; upi_wallet_paytm: number; refund: number; refund_processed: number; total: number; }; product_sales_yearly?: { year: string; amount: number }[]; sales_yearly?: { year: string; amount: number }[]; top_selling_products?: { product_name: string; quantity: number }[]; error?: string; } @Component({ selector: 'dashboard-example', standalone: true, templateUrl: './example.component.html', encapsulation: ViewEncapsulation.None, imports: [ RouterModule, CommonModule, TranslocoModule, MatIconModule, MatButtonModule, MatRippleModule, MatMenuModule, MatTabsModule, MatButtonToggleModule, NgApexchartsModule, MatTableModule, ] }) export class ExampleComponent implements OnInit { machineTitle: string = 'Loading...'; machineCount: number = 0; clients: number = 0; companyUsers: number = 0; clientUsers: number = 0; transactions: number = 0; sales: string = '₹0.00'; activeMachines: number = 0; inactiveMachines: number = 0; errorMessage: string = ''; loading: boolean = false; // Payment breakdown paymentCash: number = 0; paymentCashless: number = 0; paymentUPIWalletCard: number = 0; paymentUPIWalletPaytm: number = 0; refund: number = 0; refundProcessed: number = 0; paymentTotal: number = 0; // Machine operation status machineOperationStatus: { [key: string]: number } = {}; // Machine stock status machineStockStatus: { [key: string]: number } = {}; // Top selling products topSellingProducts: { product_name: string; quantity: number }[] = []; // Filter states productSalesTimeRange: string = 'year'; salesTimeRange: string = 'year'; topProductsTimeRange: string = 'year'; productSalesChartOptions: Partial; salesChartOptions: Partial; private readonly baseUrl = environment.apiUrl || 'http://localhost:5000'; constructor(private http: HttpClient) { this.initializeCharts(); } ngOnInit() { this.fetchDashboardMetrics('all'); } updateMachine(filter: 'active' | 'inactive' | 'all') { console.log('Filter clicked:', filter); this.fetchDashboardMetrics(filter); } updateProductSalesTimeRange(range: string) { this.productSalesTimeRange = range; console.log('Product Sales Time Range:', range); this.fetchProductSalesData(range); } updateSalesTimeRange(range: string) { this.salesTimeRange = range; console.log('Sales Time Range:', range); this.fetchSalesData(range); } updateTopProductsTimeRange(range: string) { this.topProductsTimeRange = range; console.log('Top Products Time Range:', range); this.fetchTopProductsData(range); } private fetchProductSalesData(timeRange: string) { const url = `${this.baseUrl}/product-sales?time_range=${timeRange}`; console.log('Fetching product sales data:', timeRange); const token = localStorage.getItem('token') || localStorage.getItem('access_token'); this.http.get<{ product_sales: { year: string; amount: number }[] }>(url, { headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'Authorization': token ? `Bearer ${token}` : '' } }).subscribe({ next: (response) => { console.log('✓ Product Sales Data:', response); if (response.product_sales && response.product_sales.length > 0) { this.productSalesChartOptions = { ...this.productSalesChartOptions, series: [{ name: 'Sales', data: response.product_sales.map(item => item.amount) }], xaxis: { ...this.productSalesChartOptions.xaxis, categories: response.product_sales.map(item => item.year) } }; } }, error: (error) => { console.error('✗ Error fetching product sales data:', error); } }); } private fetchSalesData(timeRange: string) { const url = `${this.baseUrl}/sales-data?time_range=${timeRange}`; console.log('Fetching sales data:', timeRange); const token = localStorage.getItem('token') || localStorage.getItem('access_token'); this.http.get<{ sales_data: { year: string; amount: number }[] }>(url, { headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'Authorization': token ? `Bearer ${token}` : '' } }).subscribe({ next: (response) => { console.log('✓ Sales Data:', response); if (response.sales_data && response.sales_data.length > 0) { this.salesChartOptions = { ...this.salesChartOptions, series: [{ name: 'Sales', data: response.sales_data.map(item => item.amount) }], xaxis: { ...this.salesChartOptions.xaxis, categories: response.sales_data.map(item => item.year) } }; } }, error: (error) => { console.error('✗ Error fetching sales data:', error); } }); } private fetchTopProductsData(timeRange: string) { const url = `${this.baseUrl}/top-products?time_range=${timeRange}`; console.log('Fetching top products data:', timeRange); const token = localStorage.getItem('token') || localStorage.getItem('access_token'); this.http.get<{ top_products: { product_name: string; quantity: number }[] }>(url, { headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'Authorization': token ? `Bearer ${token}` : '' } }).subscribe({ next: (response) => { console.log('✓ Top Products Data:', response); if (response.top_products) { this.topSellingProducts = response.top_products; } }, error: (error) => { console.error('✗ Error fetching top products data:', error); } }); } private initializeCharts() { // Product Sales Chart this.productSalesChartOptions = { series: [{ name: 'Sales', data: [] }], chart: { type: 'bar', height: 350, stacked: true, toolbar: { show: false } }, plotOptions: { bar: { horizontal: false, columnWidth: '80%', } }, dataLabels: { enabled: false }, xaxis: { categories: [], labels: { style: { fontSize: '12px' } } }, yaxis: { title: { text: 'Rupees' }, labels: { formatter: (val) => `₹${(val / 1000000).toFixed(1)}M` } }, colors: ['#3B82F6', '#EF4444', '#F59E0B', '#10B981', '#8B5CF6'], fill: { opacity: 1 }, legend: { show: false }, grid: { borderColor: '#f1f1f1' } }; // Sales Chart (Area) this.salesChartOptions = { series: [{ name: 'Sales', data: [] }], chart: { type: 'area', height: 350, toolbar: { show: false } }, dataLabels: { enabled: false }, stroke: { curve: 'smooth', width: 2 }, xaxis: { categories: [], labels: { style: { fontSize: '12px' } } }, yaxis: { title: { text: 'Rupees' }, labels: { formatter: (val) => `₹${(val / 1000000).toFixed(1)}M` } }, colors: ['#14B8A6'], fill: { type: 'gradient', gradient: { shadeIntensity: 1, opacityFrom: 0.7, opacityTo: 0.3, stops: [0, 90, 100] } }, grid: { borderColor: '#f1f1f1' } }; } private fetchDashboardMetrics(filter: string) { this.loading = true; this.machineTitle = 'Loading...'; this.errorMessage = ''; const url = `${this.baseUrl}/dashboard-metrics?machine_filter=${filter}`; console.log('Fetching dashboard data with filter:', filter); console.log('URL:', url); // Get token from localStorage const token = localStorage.getItem('token') || localStorage.getItem('access_token'); this.http.get(url, { headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'Authorization': token ? `Bearer ${token}` : '' } }).subscribe({ next: (response) => { console.log('✓ Dashboard API Response:', response); this.loading = false; if (response.error) { this.handleError(`Server Error: ${response.error}`); } else { this.updateDashboardData(response); console.log('✓ Dashboard data updated successfully'); } }, error: (error: HttpErrorResponse) => { console.error('✗ Dashboard HTTP Error:', error); this.loading = false; this.handleHttpError(error); } }); } private updateDashboardData(response: DashboardMetrics) { // Update basic metrics this.machineTitle = response.machine_title || 'Machines'; this.machineCount = response.machine_count || 0; this.clients = response.clients || 0; this.companyUsers = response.company_users || 0; this.clientUsers = response.client_users || 0; this.transactions = response.transactions || 0; this.sales = `₹${response.sales || '0.00'}`; this.activeMachines = response.active_machines || 0; this.inactiveMachines = response.inactive_machines || 0; this.errorMessage = ''; // Update payment breakdown if (response.payment_breakdown) { this.paymentCash = response.payment_breakdown.cash || 0; this.paymentCashless = response.payment_breakdown.cashless || 0; this.paymentUPIWalletCard = response.payment_breakdown.upi_wallet_card || 0; this.paymentUPIWalletPaytm = response.payment_breakdown.upi_wallet_paytm || 0; this.refund = response.payment_breakdown.refund || 0; this.refundProcessed = response.payment_breakdown.refund_processed || 0; this.paymentTotal = response.payment_breakdown.total || 0; } // Update machine operation status if (response.machine_operation_status) { this.machineOperationStatus = response.machine_operation_status; } // Update machine stock status if (response.machine_stock_status) { this.machineStockStatus = response.machine_stock_status; } // Update top selling products if (response.top_selling_products) { this.topSellingProducts = response.top_selling_products; } // Update Product Sales Chart if (response.product_sales_yearly && response.product_sales_yearly.length > 0) { this.productSalesChartOptions = { ...this.productSalesChartOptions, series: [{ name: 'Sales', data: response.product_sales_yearly.map(item => item.amount) }], xaxis: { ...this.productSalesChartOptions.xaxis, categories: response.product_sales_yearly.map(item => item.year) } }; } // Update Sales Chart if (response.sales_yearly && response.sales_yearly.length > 0) { this.salesChartOptions = { ...this.salesChartOptions, series: [{ name: 'Sales', data: response.sales_yearly.map(item => item.amount) }], xaxis: { ...this.salesChartOptions.xaxis, categories: response.sales_yearly.map(item => item.year) } }; } } getStockStatusKeys(): string[] { return Object.keys(this.machineStockStatus).sort(); } getOperationStatusKeys(): string[] { return Object.keys(this.machineOperationStatus).sort(); } private handleError(message: string) { this.errorMessage = message; this.resetDashboardData(); } private handleHttpError(error: HttpErrorResponse) { let errorMessage = 'Failed to load data. '; if (error.status === 0) { errorMessage += 'Cannot connect to server. Please check if the Flask server is running on the correct port.'; } else if (error.status === 401) { errorMessage += 'Authentication required. Please login again.'; } else if (error.status === 404) { errorMessage += 'API endpoint not found. Please check the server routing.'; } else if (error.status >= 500) { errorMessage += 'Internal server error. Please check the server logs.'; } else { errorMessage += `Status: ${error.status} - ${error.statusText}. `; errorMessage += `Details: ${error.error?.error || error.message || 'Unknown error'}`; } this.handleError(errorMessage); } private resetDashboardData() { this.machineTitle = 'Error'; this.machineCount = 0; this.clients = 0; this.companyUsers = 0; this.clientUsers = 0; this.transactions = 0; this.sales = '₹0.00'; this.activeMachines = 0; this.inactiveMachines = 0; this.paymentCash = 0; this.paymentCashless = 0; this.paymentUPIWalletCard = 0; this.paymentUPIWalletPaytm = 0; this.refund = 0; this.refundProcessed = 0; this.paymentTotal = 0; this.machineOperationStatus = {}; this.machineStockStatus = {}; this.topSellingProducts = []; } retryFetch() { this.fetchDashboardMetrics('all'); } }