Dashboard update
@ -6,14 +6,14 @@ PAYU_MERCHANT_SALT=DusXSSjqqSMTPSpw32hlFXF6LKY2Zm3y
|
|||||||
PAYU_SUCCESS_URL=http://localhost:4200/payment-success
|
PAYU_SUCCESS_URL=http://localhost:4200/payment-success
|
||||||
PAYU_FAILURE_URL=http://localhost:4200/payment-failure
|
PAYU_FAILURE_URL=http://localhost:4200/payment-failure
|
||||||
|
|
||||||
FLASK_ENV=production
|
FLASK_ENV=development
|
||||||
|
|
||||||
MYSQL_HOST=db
|
MYSQL_HOST=db
|
||||||
MYSQL_USER=vendinguser
|
MYSQL_USER=vendinguser
|
||||||
MYSQL_PASSWORD=vendingpass
|
MYSQL_PASSWORD=vendingpass
|
||||||
MYSQL_DATABASE=vending
|
MYSQL_DATABASE=vending
|
||||||
|
|
||||||
SQLITE_DB_PATH=machines
|
SQLITE_DB_PATH=machines.db
|
||||||
|
|
||||||
BREVO_SMTP_EMAIL=smukeshsn2000@gmail.com
|
BREVO_SMTP_EMAIL=smukeshsn2000@gmail.com
|
||||||
BREVO_SMTP_KEY=your-brevo-smtp-key
|
BREVO_SMTP_KEY=your-brevo-smtp-key
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
from app import db # Add this if not already there
|
from app import db # Add this if not already there
|
||||||
from sqlalchemy import func # Add this
|
from sqlalchemy import func, extract # Add this
|
||||||
from flask import Blueprint, request, jsonify, send_from_directory
|
from flask import Blueprint, request, jsonify, send_from_directory
|
||||||
from app.services.services import MachineService, UserService, ProductService, serial_service, TransactionService, RoleService
|
from app.services.services import MachineService, UserService, ProductService, serial_service, TransactionService, RoleService
|
||||||
from app.models.models import Machine, User, Transaction, Product, VendingSlot, Role
|
from app.models.models import Machine, User, Transaction, Product, VendingSlot, Role
|
||||||
@ -853,6 +853,10 @@ def get_current_user_from_token():
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
from flask import jsonify, request
|
||||||
|
from sqlalchemy import func, extract
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
@bp.route('/dashboard-metrics', methods=['GET'])
|
@bp.route('/dashboard-metrics', methods=['GET'])
|
||||||
def get_dashboard_metrics_role_based():
|
def get_dashboard_metrics_role_based():
|
||||||
"""Get dashboard metrics filtered by user role"""
|
"""Get dashboard metrics filtered by user role"""
|
||||||
@ -877,16 +881,12 @@ def get_dashboard_metrics_role_based():
|
|||||||
|
|
||||||
# ROLE-BASED FILTERING
|
# ROLE-BASED FILTERING
|
||||||
if user_role in ['Management', 'SuperAdmin', 'Admin']:
|
if user_role in ['Management', 'SuperAdmin', 'Admin']:
|
||||||
# Show overall system stats
|
|
||||||
response = get_admin_dashboard_metrics(filter_type)
|
response = get_admin_dashboard_metrics(filter_type)
|
||||||
elif user_role == 'Client':
|
elif user_role == 'Client':
|
||||||
# Show only this client's data
|
|
||||||
response = get_Client_dashboard_metrics(current_user.id, filter_type)
|
response = get_Client_dashboard_metrics(current_user.id, filter_type)
|
||||||
elif user_role == 'Refiller':
|
elif user_role == 'Refiller':
|
||||||
# Show product/warehouse related stats
|
|
||||||
response = get_refiller_dashboard_metrics(filter_type)
|
response = get_refiller_dashboard_metrics(filter_type)
|
||||||
elif user_role == 'Servicer':
|
elif user_role == 'Servicer':
|
||||||
# Show machine maintenance stats
|
|
||||||
response = get_servicer_dashboard_metrics(filter_type)
|
response = get_servicer_dashboard_metrics(filter_type)
|
||||||
else:
|
else:
|
||||||
return jsonify({'error': 'Invalid role'}), 403
|
return jsonify({'error': 'Invalid role'}), 403
|
||||||
@ -914,23 +914,52 @@ def get_admin_dashboard_metrics(filter_type):
|
|||||||
machine_count = Machine.query.count()
|
machine_count = Machine.query.count()
|
||||||
machine_title = 'All Machines'
|
machine_title = 'All Machines'
|
||||||
|
|
||||||
# Client count (users with role 'Client')
|
# Client count
|
||||||
clients = User.query.filter_by(roles='Client').count()
|
clients = User.query.filter_by(roles='Client').count()
|
||||||
|
|
||||||
# Company users (Admin, SuperAdmin, Management, Refiller, Servicer)
|
# Company users
|
||||||
company_users = User.query.filter(
|
company_users = User.query.filter(
|
||||||
User.roles.in_(['Management', 'SuperAdmin', 'Admin', 'Refiller', 'Servicer'])
|
User.roles.in_(['Management', 'SuperAdmin', 'Admin', 'Refiller', 'Servicer'])
|
||||||
).count()
|
).count()
|
||||||
|
|
||||||
|
# Active and inactive machines
|
||||||
|
active_machines = Machine.query.filter_by(operation_status='active').count()
|
||||||
|
inactive_machines = Machine.query.filter_by(operation_status='inactive').count()
|
||||||
|
|
||||||
# COUNT TRANSACTIONS AND CALCULATE SALES
|
# COUNT TRANSACTIONS AND CALCULATE SALES
|
||||||
transactions_count = Transaction.query.count()
|
transactions_count = Transaction.query.count()
|
||||||
|
|
||||||
# Calculate total sales (sum of all successful transaction amounts)
|
# Calculate total sales
|
||||||
from sqlalchemy import func
|
|
||||||
total_sales = db.session.query(func.sum(Transaction.amount))\
|
total_sales = db.session.query(func.sum(Transaction.amount))\
|
||||||
.filter(Transaction.status == 'Success')\
|
.filter(Transaction.status == 'Success')\
|
||||||
.scalar() or 0.0
|
.scalar() or 0.0
|
||||||
|
|
||||||
|
# PAYMENT BREAKDOWN
|
||||||
|
payment_breakdown = get_payment_breakdown()
|
||||||
|
|
||||||
|
# MACHINE OPERATION STATUS
|
||||||
|
machine_operation_status = {
|
||||||
|
'Online': Machine.query.filter_by(connection_status='online').count(),
|
||||||
|
'Offline': Machine.query.filter_by(connection_status='offline').count(),
|
||||||
|
'Down (Planned)': Machine.query.filter_by(operation_status='down_planned').count(),
|
||||||
|
'Down': Machine.query.filter_by(operation_status='down').count(),
|
||||||
|
'Standby': Machine.query.filter_by(operation_status='standby').count(),
|
||||||
|
'Terminated': Machine.query.filter_by(operation_status='terminated').count(),
|
||||||
|
'N/A': Machine.query.filter(Machine.operation_status.is_(None)).count()
|
||||||
|
}
|
||||||
|
|
||||||
|
# MACHINE STOCK STATUS - Calculate based on vending slots
|
||||||
|
machine_stock_status = calculate_machine_stock_status()
|
||||||
|
|
||||||
|
# PRODUCT SALES YEARLY
|
||||||
|
product_sales_yearly = get_product_sales_yearly()
|
||||||
|
|
||||||
|
# SALES YEARLY
|
||||||
|
sales_yearly = get_sales_yearly()
|
||||||
|
|
||||||
|
# TOP SELLING PRODUCTS
|
||||||
|
top_selling_products = get_top_selling_products()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'machine_title': machine_title,
|
'machine_title': machine_title,
|
||||||
'machine_count': machine_count,
|
'machine_count': machine_count,
|
||||||
@ -939,13 +968,21 @@ def get_admin_dashboard_metrics(filter_type):
|
|||||||
'client_users': clients,
|
'client_users': clients,
|
||||||
'transactions': transactions_count,
|
'transactions': transactions_count,
|
||||||
'sales': f'{total_sales:.2f}',
|
'sales': f'{total_sales:.2f}',
|
||||||
|
'active_machines': active_machines,
|
||||||
|
'inactive_machines': inactive_machines,
|
||||||
|
'payment_breakdown': payment_breakdown,
|
||||||
|
'machine_operation_status': machine_operation_status,
|
||||||
|
'machine_stock_status': machine_stock_status,
|
||||||
|
'product_sales_yearly': product_sales_yearly,
|
||||||
|
'sales_yearly': sales_yearly,
|
||||||
|
'top_selling_products': top_selling_products,
|
||||||
'user_role': 'admin',
|
'user_role': 'admin',
|
||||||
'user_type': 'Overall System'
|
'user_type': 'Overall System'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_Client_dashboard_metrics(user_id, filter_type):
|
def get_Client_dashboard_metrics(user_id, filter_type):
|
||||||
"""Get metrics for Client/Client - Only their own data"""
|
"""Get metrics for Client - Only their own data"""
|
||||||
# Get only machines belonging to this client
|
# Get only machines belonging to this client
|
||||||
if filter_type == 'active':
|
if filter_type == 'active':
|
||||||
machine_count = Machine.query.filter_by(
|
machine_count = Machine.query.filter_by(
|
||||||
@ -963,28 +1000,53 @@ def get_Client_dashboard_metrics(user_id, filter_type):
|
|||||||
machine_count = Machine.query.filter_by(client_id=user_id).count()
|
machine_count = Machine.query.filter_by(client_id=user_id).count()
|
||||||
machine_title = 'My Machines'
|
machine_title = 'My Machines'
|
||||||
|
|
||||||
# For Client, clients = 1 (themselves)
|
|
||||||
clients = 1
|
clients = 1
|
||||||
|
|
||||||
# Company users not relevant for Client
|
|
||||||
company_users = 0
|
company_users = 0
|
||||||
|
|
||||||
# GET THIS CLIENT'S TRANSACTIONS AND SALES
|
|
||||||
# Get machine IDs for this client
|
# Get machine IDs for this client
|
||||||
client_machine_ids = [m.machine_id for m in Machine.query.filter_by(client_id=user_id).all()]
|
client_machine_ids = [m.machine_id for m in Machine.query.filter_by(client_id=user_id).all()]
|
||||||
|
|
||||||
|
# Active and inactive machines for this client
|
||||||
|
active_machines = Machine.query.filter_by(client_id=user_id, operation_status='active').count()
|
||||||
|
inactive_machines = Machine.query.filter_by(client_id=user_id, operation_status='inactive').count()
|
||||||
|
|
||||||
# Count transactions for this client's machines
|
# Count transactions for this client's machines
|
||||||
transactions_count = Transaction.query.filter(
|
transactions_count = Transaction.query.filter(
|
||||||
Transaction.machine_id.in_(client_machine_ids)
|
Transaction.machine_id.in_(client_machine_ids)
|
||||||
).count() if client_machine_ids else 0
|
).count() if client_machine_ids else 0
|
||||||
|
|
||||||
# Calculate sales for this client's machines
|
# Calculate sales for this client's machines
|
||||||
from sqlalchemy import func
|
|
||||||
total_sales = db.session.query(func.sum(Transaction.amount))\
|
total_sales = db.session.query(func.sum(Transaction.amount))\
|
||||||
.filter(Transaction.machine_id.in_(client_machine_ids))\
|
.filter(Transaction.machine_id.in_(client_machine_ids))\
|
||||||
.filter(Transaction.status == 'Success')\
|
.filter(Transaction.status == 'Success')\
|
||||||
.scalar() or 0.0 if client_machine_ids else 0.0
|
.scalar() or 0.0 if client_machine_ids else 0.0
|
||||||
|
|
||||||
|
# PAYMENT BREAKDOWN for client's machines
|
||||||
|
payment_breakdown = get_payment_breakdown(client_machine_ids)
|
||||||
|
|
||||||
|
# MACHINE OPERATION STATUS for client's machines
|
||||||
|
machine_operation_status = {
|
||||||
|
'Online': Machine.query.filter(Machine.client_id == user_id, Machine.connection_status == 'online').count(),
|
||||||
|
'Offline': Machine.query.filter(Machine.client_id == user_id, Machine.connection_status == 'offline').count(),
|
||||||
|
'Down (Planned)': Machine.query.filter(Machine.client_id == user_id, Machine.operation_status == 'down_planned').count(),
|
||||||
|
'Down': Machine.query.filter(Machine.client_id == user_id, Machine.operation_status == 'down').count(),
|
||||||
|
'Standby': Machine.query.filter(Machine.client_id == user_id, Machine.operation_status == 'standby').count(),
|
||||||
|
'Terminated': Machine.query.filter(Machine.client_id == user_id, Machine.operation_status == 'terminated').count(),
|
||||||
|
'N/A': Machine.query.filter(Machine.client_id == user_id, Machine.operation_status.is_(None)).count()
|
||||||
|
}
|
||||||
|
|
||||||
|
# MACHINE STOCK STATUS for client's machines
|
||||||
|
machine_stock_status = calculate_machine_stock_status(client_machine_ids)
|
||||||
|
|
||||||
|
# PRODUCT SALES YEARLY for client's machines
|
||||||
|
product_sales_yearly = get_product_sales_yearly(client_machine_ids)
|
||||||
|
|
||||||
|
# SALES YEARLY for client's machines
|
||||||
|
sales_yearly = get_sales_yearly(client_machine_ids)
|
||||||
|
|
||||||
|
# TOP SELLING PRODUCTS for client's machines
|
||||||
|
top_selling_products = get_top_selling_products(client_machine_ids)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'machine_title': machine_title,
|
'machine_title': machine_title,
|
||||||
'machine_count': machine_count,
|
'machine_count': machine_count,
|
||||||
@ -993,6 +1055,14 @@ def get_Client_dashboard_metrics(user_id, filter_type):
|
|||||||
'client_users': clients,
|
'client_users': clients,
|
||||||
'transactions': transactions_count,
|
'transactions': transactions_count,
|
||||||
'sales': f'{total_sales:.2f}',
|
'sales': f'{total_sales:.2f}',
|
||||||
|
'active_machines': active_machines,
|
||||||
|
'inactive_machines': inactive_machines,
|
||||||
|
'payment_breakdown': payment_breakdown,
|
||||||
|
'machine_operation_status': machine_operation_status,
|
||||||
|
'machine_stock_status': machine_stock_status,
|
||||||
|
'product_sales_yearly': product_sales_yearly,
|
||||||
|
'sales_yearly': sales_yearly,
|
||||||
|
'top_selling_products': top_selling_products,
|
||||||
'user_role': 'Client',
|
'user_role': 'Client',
|
||||||
'user_type': 'My Business'
|
'user_type': 'My Business'
|
||||||
}
|
}
|
||||||
@ -1000,21 +1070,32 @@ def get_Client_dashboard_metrics(user_id, filter_type):
|
|||||||
|
|
||||||
def get_refiller_dashboard_metrics(filter_type):
|
def get_refiller_dashboard_metrics(filter_type):
|
||||||
"""Get metrics for Refiller - Product/warehouse focused"""
|
"""Get metrics for Refiller - Product/warehouse focused"""
|
||||||
# Machines they need to service
|
|
||||||
machine_count = Machine.query.count()
|
machine_count = Machine.query.count()
|
||||||
|
|
||||||
# Products in warehouse
|
|
||||||
product_count = Product.query.count()
|
product_count = Product.query.count()
|
||||||
|
|
||||||
# Transaction count (all)
|
|
||||||
transactions_count = Transaction.query.count()
|
transactions_count = Transaction.query.count()
|
||||||
|
|
||||||
# Total sales
|
|
||||||
from sqlalchemy import func
|
|
||||||
total_sales = db.session.query(func.sum(Transaction.amount))\
|
total_sales = db.session.query(func.sum(Transaction.amount))\
|
||||||
.filter(Transaction.status == 'Success')\
|
.filter(Transaction.status == 'Success')\
|
||||||
.scalar() or 0.0
|
.scalar() or 0.0
|
||||||
|
|
||||||
|
active_machines = Machine.query.filter_by(operation_status='active').count()
|
||||||
|
inactive_machines = Machine.query.filter_by(operation_status='inactive').count()
|
||||||
|
|
||||||
|
payment_breakdown = get_payment_breakdown()
|
||||||
|
machine_operation_status = {
|
||||||
|
'Online': Machine.query.filter_by(connection_status='online').count(),
|
||||||
|
'Offline': Machine.query.filter_by(connection_status='offline').count(),
|
||||||
|
'Down (Planned)': Machine.query.filter_by(operation_status='down_planned').count(),
|
||||||
|
'Down': Machine.query.filter_by(operation_status='down').count(),
|
||||||
|
'Standby': Machine.query.filter_by(operation_status='standby').count(),
|
||||||
|
'Terminated': Machine.query.filter_by(operation_status='terminated').count(),
|
||||||
|
'N/A': Machine.query.filter(Machine.operation_status.is_(None)).count()
|
||||||
|
}
|
||||||
|
machine_stock_status = calculate_machine_stock_status()
|
||||||
|
product_sales_yearly = get_product_sales_yearly()
|
||||||
|
sales_yearly = get_sales_yearly()
|
||||||
|
top_selling_products = get_top_selling_products()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'machine_title': 'Machines to Service',
|
'machine_title': 'Machines to Service',
|
||||||
'machine_count': machine_count,
|
'machine_count': machine_count,
|
||||||
@ -1023,7 +1104,15 @@ def get_refiller_dashboard_metrics(filter_type):
|
|||||||
'client_users': 0,
|
'client_users': 0,
|
||||||
'transactions': transactions_count,
|
'transactions': transactions_count,
|
||||||
'sales': f'{total_sales:.2f}',
|
'sales': f'{total_sales:.2f}',
|
||||||
|
'active_machines': active_machines,
|
||||||
|
'inactive_machines': inactive_machines,
|
||||||
'product_count': product_count,
|
'product_count': product_count,
|
||||||
|
'payment_breakdown': payment_breakdown,
|
||||||
|
'machine_operation_status': machine_operation_status,
|
||||||
|
'machine_stock_status': machine_stock_status,
|
||||||
|
'product_sales_yearly': product_sales_yearly,
|
||||||
|
'sales_yearly': sales_yearly,
|
||||||
|
'top_selling_products': top_selling_products,
|
||||||
'user_role': 'refiller',
|
'user_role': 'refiller',
|
||||||
'user_type': 'Inventory Management'
|
'user_type': 'Inventory Management'
|
||||||
}
|
}
|
||||||
@ -1031,7 +1120,6 @@ def get_refiller_dashboard_metrics(filter_type):
|
|||||||
|
|
||||||
def get_servicer_dashboard_metrics(filter_type):
|
def get_servicer_dashboard_metrics(filter_type):
|
||||||
"""Get metrics for Servicer - Machine maintenance focused"""
|
"""Get metrics for Servicer - Machine maintenance focused"""
|
||||||
# All machines they can service
|
|
||||||
if filter_type == 'active':
|
if filter_type == 'active':
|
||||||
machine_count = Machine.query.filter_by(operation_status='active').count()
|
machine_count = Machine.query.filter_by(operation_status='active').count()
|
||||||
machine_title = 'Active Machines'
|
machine_title = 'Active Machines'
|
||||||
@ -1042,18 +1130,31 @@ def get_servicer_dashboard_metrics(filter_type):
|
|||||||
machine_count = Machine.query.count()
|
machine_count = Machine.query.count()
|
||||||
machine_title = 'Total Machines'
|
machine_title = 'Total Machines'
|
||||||
|
|
||||||
# Machines needing maintenance
|
|
||||||
maintenance_count = Machine.query.filter_by(operation_status='maintenance').count()
|
maintenance_count = Machine.query.filter_by(operation_status='maintenance').count()
|
||||||
|
|
||||||
# Transaction count (all)
|
|
||||||
transactions_count = Transaction.query.count()
|
transactions_count = Transaction.query.count()
|
||||||
|
|
||||||
# Total sales
|
|
||||||
from sqlalchemy import func
|
|
||||||
total_sales = db.session.query(func.sum(Transaction.amount))\
|
total_sales = db.session.query(func.sum(Transaction.amount))\
|
||||||
.filter(Transaction.status == 'Success')\
|
.filter(Transaction.status == 'Success')\
|
||||||
.scalar() or 0.0
|
.scalar() or 0.0
|
||||||
|
|
||||||
|
active_machines = Machine.query.filter_by(operation_status='active').count()
|
||||||
|
inactive_machines = Machine.query.filter_by(operation_status='inactive').count()
|
||||||
|
|
||||||
|
payment_breakdown = get_payment_breakdown()
|
||||||
|
machine_operation_status = {
|
||||||
|
'Online': Machine.query.filter_by(connection_status='online').count(),
|
||||||
|
'Offline': Machine.query.filter_by(connection_status='offline').count(),
|
||||||
|
'Down (Planned)': Machine.query.filter_by(operation_status='down_planned').count(),
|
||||||
|
'Down': Machine.query.filter_by(operation_status='down').count(),
|
||||||
|
'Standby': Machine.query.filter_by(operation_status='standby').count(),
|
||||||
|
'Terminated': Machine.query.filter_by(operation_status='terminated').count(),
|
||||||
|
'N/A': Machine.query.filter(Machine.operation_status.is_(None)).count()
|
||||||
|
}
|
||||||
|
machine_stock_status = calculate_machine_stock_status()
|
||||||
|
product_sales_yearly = get_product_sales_yearly()
|
||||||
|
sales_yearly = get_sales_yearly()
|
||||||
|
top_selling_products = get_top_selling_products()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'machine_title': machine_title,
|
'machine_title': machine_title,
|
||||||
'machine_count': machine_count,
|
'machine_count': machine_count,
|
||||||
@ -1062,11 +1163,374 @@ def get_servicer_dashboard_metrics(filter_type):
|
|||||||
'client_users': 0,
|
'client_users': 0,
|
||||||
'transactions': transactions_count,
|
'transactions': transactions_count,
|
||||||
'sales': f'{total_sales:.2f}',
|
'sales': f'{total_sales:.2f}',
|
||||||
|
'active_machines': active_machines,
|
||||||
|
'inactive_machines': inactive_machines,
|
||||||
'maintenance_count': maintenance_count,
|
'maintenance_count': maintenance_count,
|
||||||
|
'payment_breakdown': payment_breakdown,
|
||||||
|
'machine_operation_status': machine_operation_status,
|
||||||
|
'machine_stock_status': machine_stock_status,
|
||||||
|
'product_sales_yearly': product_sales_yearly,
|
||||||
|
'sales_yearly': sales_yearly,
|
||||||
|
'top_selling_products': top_selling_products,
|
||||||
'user_role': 'servicer',
|
'user_role': 'servicer',
|
||||||
'user_type': 'Machine Maintenance'
|
'user_type': 'Machine Maintenance'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# HELPER FUNCTIONS
|
||||||
|
|
||||||
|
def get_payment_breakdown(machine_ids=None):
|
||||||
|
"""Calculate payment breakdown from transactions"""
|
||||||
|
query = db.session.query(
|
||||||
|
Transaction.payment_type,
|
||||||
|
func.sum(Transaction.amount).label('total')
|
||||||
|
)
|
||||||
|
|
||||||
|
if machine_ids:
|
||||||
|
query = query.filter(Transaction.machine_id.in_(machine_ids))
|
||||||
|
|
||||||
|
query = query.filter(Transaction.status == 'Success')\
|
||||||
|
.group_by(Transaction.payment_type)
|
||||||
|
|
||||||
|
payment_data = query.all()
|
||||||
|
|
||||||
|
breakdown = {
|
||||||
|
'cash': 0.0,
|
||||||
|
'cashless': 0.0,
|
||||||
|
'upi_wallet_card': 0.0,
|
||||||
|
'upi_wallet_paytm': 0.0,
|
||||||
|
'refund': 0.0,
|
||||||
|
'refund_processed': 0.0,
|
||||||
|
'total': 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
for payment_type, total in payment_data:
|
||||||
|
if payment_type and total:
|
||||||
|
payment_type_lower = payment_type.lower()
|
||||||
|
if 'cash' in payment_type_lower and 'cashless' not in payment_type_lower:
|
||||||
|
breakdown['cash'] += float(total)
|
||||||
|
elif 'cashless' in payment_type_lower:
|
||||||
|
breakdown['cashless'] += float(total)
|
||||||
|
elif 'phonepe' in payment_type_lower or 'card' in payment_type_lower:
|
||||||
|
breakdown['upi_wallet_card'] += float(total)
|
||||||
|
elif 'paytm' in payment_type_lower:
|
||||||
|
breakdown['upi_wallet_paytm'] += float(total)
|
||||||
|
|
||||||
|
# Calculate refunds
|
||||||
|
refund_query = db.session.query(func.sum(Transaction.return_amount))
|
||||||
|
if machine_ids:
|
||||||
|
refund_query = refund_query.filter(Transaction.machine_id.in_(machine_ids))
|
||||||
|
|
||||||
|
breakdown['refund'] = float(refund_query.filter(
|
||||||
|
Transaction.amount_receiving_status == 'Dispense failed refund'
|
||||||
|
).scalar() or 0.0)
|
||||||
|
|
||||||
|
breakdown['refund_processed'] = float(refund_query.filter(
|
||||||
|
Transaction.amount_receiving_status == 'Refund processed'
|
||||||
|
).scalar() or 0.0)
|
||||||
|
|
||||||
|
breakdown['total'] = sum([
|
||||||
|
breakdown['cash'],
|
||||||
|
breakdown['cashless'],
|
||||||
|
breakdown['upi_wallet_card'],
|
||||||
|
breakdown['upi_wallet_paytm']
|
||||||
|
])
|
||||||
|
|
||||||
|
return breakdown
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_machine_stock_status(machine_ids=None):
|
||||||
|
"""Calculate machine stock status based on vending slots"""
|
||||||
|
query = Machine.query
|
||||||
|
if machine_ids:
|
||||||
|
query = query.filter(Machine.machine_id.in_(machine_ids))
|
||||||
|
|
||||||
|
machines = query.all()
|
||||||
|
|
||||||
|
status_counts = {
|
||||||
|
'75 - 100%': 0,
|
||||||
|
'50 - 75%': 0,
|
||||||
|
'25 - 50%': 0,
|
||||||
|
'0 - 25%': 0,
|
||||||
|
'N/A': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
for machine in machines:
|
||||||
|
slots = VendingSlot.query.filter_by(machine_id=machine.machine_id).all()
|
||||||
|
if not slots:
|
||||||
|
status_counts['N/A'] += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
total_slots = len(slots)
|
||||||
|
filled_slots = sum(1 for slot in slots if slot.units and slot.units > 0)
|
||||||
|
|
||||||
|
if total_slots == 0:
|
||||||
|
status_counts['N/A'] += 1
|
||||||
|
else:
|
||||||
|
fill_percentage = (filled_slots / total_slots) * 100
|
||||||
|
if fill_percentage >= 75:
|
||||||
|
status_counts['75 - 100%'] += 1
|
||||||
|
elif fill_percentage >= 50:
|
||||||
|
status_counts['50 - 75%'] += 1
|
||||||
|
elif fill_percentage >= 25:
|
||||||
|
status_counts['25 - 50%'] += 1
|
||||||
|
elif fill_percentage > 0:
|
||||||
|
status_counts['0 - 25%'] += 1
|
||||||
|
else:
|
||||||
|
status_counts['0 - 25%'] += 1
|
||||||
|
|
||||||
|
return status_counts
|
||||||
|
|
||||||
|
|
||||||
|
def get_product_sales_yearly(machine_ids=None):
|
||||||
|
"""Get product sales aggregated by year"""
|
||||||
|
query = db.session.query(
|
||||||
|
extract('year', Transaction.created_at).label('year'),
|
||||||
|
func.sum(Transaction.amount).label('total_sales')
|
||||||
|
)
|
||||||
|
|
||||||
|
if machine_ids:
|
||||||
|
query = query.filter(Transaction.machine_id.in_(machine_ids))
|
||||||
|
|
||||||
|
query = query.filter(Transaction.status == 'Success')\
|
||||||
|
.group_by(extract('year', Transaction.created_at))\
|
||||||
|
.order_by('year')
|
||||||
|
|
||||||
|
results = query.all()
|
||||||
|
|
||||||
|
return [{'year': str(int(year)), 'amount': float(total_sales)}
|
||||||
|
for year, total_sales in results if year]
|
||||||
|
|
||||||
|
|
||||||
|
def get_sales_yearly(machine_ids=None):
|
||||||
|
"""Get sales aggregated by year"""
|
||||||
|
return get_product_sales_yearly(machine_ids)
|
||||||
|
|
||||||
|
|
||||||
|
def get_top_selling_products(machine_ids=None, limit=10):
|
||||||
|
"""Get top selling products"""
|
||||||
|
query = db.session.query(
|
||||||
|
Transaction.product_name,
|
||||||
|
func.sum(Transaction.quantity).label('total_quantity')
|
||||||
|
)
|
||||||
|
|
||||||
|
if machine_ids:
|
||||||
|
query = query.filter(Transaction.machine_id.in_(machine_ids))
|
||||||
|
|
||||||
|
query = query.filter(Transaction.status == 'Success')\
|
||||||
|
.group_by(Transaction.product_name)\
|
||||||
|
.order_by(func.sum(Transaction.quantity).desc())\
|
||||||
|
.limit(limit)
|
||||||
|
|
||||||
|
results = query.all()
|
||||||
|
|
||||||
|
return [{'product_name': product_name, 'quantity': int(total_quantity)}
|
||||||
|
for product_name, total_quantity in results]
|
||||||
|
|
||||||
|
@bp.route('/product-sales', methods=['GET'])
|
||||||
|
def get_product_sales_filtered():
|
||||||
|
"""Get product sales data filtered by time range"""
|
||||||
|
try:
|
||||||
|
current_user = get_current_user_from_token()
|
||||||
|
if not current_user:
|
||||||
|
return jsonify({'error': 'Authentication required'}), 401
|
||||||
|
|
||||||
|
time_range = request.args.get('time_range', 'year')
|
||||||
|
user_role = current_user.roles
|
||||||
|
|
||||||
|
print(f"Product Sales Filter - User: {current_user.username}, Range: {time_range}")
|
||||||
|
|
||||||
|
# Get machine IDs based on role
|
||||||
|
machine_ids = None
|
||||||
|
if user_role == 'Client':
|
||||||
|
machine_ids = [m.machine_id for m in Machine.query.filter_by(client_id=current_user.id).all()]
|
||||||
|
|
||||||
|
# Fetch data based on time range
|
||||||
|
product_sales = get_sales_data_by_range(time_range, machine_ids)
|
||||||
|
|
||||||
|
return jsonify({'product_sales': product_sales}), 200
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error in product sales filter: {str(e)}")
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/sales-data', methods=['GET'])
|
||||||
|
def get_sales_filtered():
|
||||||
|
"""Get sales data filtered by time range"""
|
||||||
|
try:
|
||||||
|
current_user = get_current_user_from_token()
|
||||||
|
if not current_user:
|
||||||
|
return jsonify({'error': 'Authentication required'}), 401
|
||||||
|
|
||||||
|
time_range = request.args.get('time_range', 'year')
|
||||||
|
user_role = current_user.roles
|
||||||
|
|
||||||
|
print(f"Sales Filter - User: {current_user.username}, Range: {time_range}")
|
||||||
|
|
||||||
|
# Get machine IDs based on role
|
||||||
|
machine_ids = None
|
||||||
|
if user_role == 'Client':
|
||||||
|
machine_ids = [m.machine_id for m in Machine.query.filter_by(client_id=current_user.id).all()]
|
||||||
|
|
||||||
|
# Fetch data based on time range
|
||||||
|
sales_data = get_sales_data_by_range(time_range, machine_ids)
|
||||||
|
|
||||||
|
return jsonify({'sales_data': sales_data}), 200
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error in sales filter: {str(e)}")
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/top-products', methods=['GET'])
|
||||||
|
def get_top_products_filtered():
|
||||||
|
"""Get top selling products filtered by time range"""
|
||||||
|
try:
|
||||||
|
current_user = get_current_user_from_token()
|
||||||
|
if not current_user:
|
||||||
|
return jsonify({'error': 'Authentication required'}), 401
|
||||||
|
|
||||||
|
time_range = request.args.get('time_range', 'year')
|
||||||
|
user_role = current_user.roles
|
||||||
|
|
||||||
|
print(f"Top Products Filter - User: {current_user.username}, Range: {time_range}")
|
||||||
|
|
||||||
|
# Get machine IDs based on role
|
||||||
|
machine_ids = None
|
||||||
|
if user_role == 'Client':
|
||||||
|
machine_ids = [m.machine_id for m in Machine.query.filter_by(client_id=current_user.id).all()]
|
||||||
|
|
||||||
|
# Fetch data based on time range
|
||||||
|
top_products = get_top_products_by_range(time_range, machine_ids)
|
||||||
|
|
||||||
|
return jsonify({'top_products': top_products}), 200
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error in top products filter: {str(e)}")
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
# Helper function to get date filter based on time range
|
||||||
|
def get_date_filter(time_range):
|
||||||
|
"""Return date filter based on time range"""
|
||||||
|
now = datetime.now()
|
||||||
|
|
||||||
|
if time_range == 'day':
|
||||||
|
# Last 24 hours
|
||||||
|
return now - timedelta(days=1)
|
||||||
|
elif time_range == 'week':
|
||||||
|
# Last 7 days
|
||||||
|
return now - timedelta(weeks=1)
|
||||||
|
elif time_range == 'month':
|
||||||
|
# Last 30 days
|
||||||
|
return now - timedelta(days=30)
|
||||||
|
elif time_range == 'year':
|
||||||
|
# All time, grouped by year
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_sales_data_by_range(time_range, machine_ids=None):
|
||||||
|
"""Get sales data aggregated by time range"""
|
||||||
|
date_filter = get_date_filter(time_range)
|
||||||
|
|
||||||
|
# Base query
|
||||||
|
query = db.session.query(Transaction.created_at, Transaction.amount)\
|
||||||
|
.filter(Transaction.status == 'Success')
|
||||||
|
|
||||||
|
# Apply machine filter if needed
|
||||||
|
if machine_ids:
|
||||||
|
query = query.filter(Transaction.machine_id.in_(machine_ids))
|
||||||
|
|
||||||
|
# Apply date filter
|
||||||
|
if date_filter:
|
||||||
|
query = query.filter(Transaction.created_at >= date_filter)
|
||||||
|
|
||||||
|
transactions = query.all()
|
||||||
|
|
||||||
|
# Aggregate based on time range
|
||||||
|
if time_range == 'day':
|
||||||
|
# Group by hour
|
||||||
|
aggregated = {}
|
||||||
|
for txn in transactions:
|
||||||
|
hour = txn.created_at.strftime('%H:00')
|
||||||
|
if hour not in aggregated:
|
||||||
|
aggregated[hour] = 0
|
||||||
|
aggregated[hour] += float(txn.amount)
|
||||||
|
|
||||||
|
return [{'year': k, 'amount': v} for k, v in sorted(aggregated.items())]
|
||||||
|
|
||||||
|
elif time_range == 'week':
|
||||||
|
# Group by day
|
||||||
|
aggregated = {}
|
||||||
|
for txn in transactions:
|
||||||
|
day = txn.created_at.strftime('%a') # Mon, Tue, etc.
|
||||||
|
if day not in aggregated:
|
||||||
|
aggregated[day] = 0
|
||||||
|
aggregated[day] += float(txn.amount)
|
||||||
|
|
||||||
|
# Sort by weekday order
|
||||||
|
days_order = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
||||||
|
return [{'year': day, 'amount': aggregated.get(day, 0)} for day in days_order]
|
||||||
|
|
||||||
|
elif time_range == 'month':
|
||||||
|
# Group by day
|
||||||
|
aggregated = {}
|
||||||
|
for txn in transactions:
|
||||||
|
day = txn.created_at.strftime('%d')
|
||||||
|
if day not in aggregated:
|
||||||
|
aggregated[day] = 0
|
||||||
|
aggregated[day] += float(txn.amount)
|
||||||
|
|
||||||
|
return [{'year': k, 'amount': v} for k, v in sorted(aggregated.items())]
|
||||||
|
|
||||||
|
else: # year or all time
|
||||||
|
# Group by year
|
||||||
|
query = db.session.query(
|
||||||
|
extract('year', Transaction.created_at).label('year'),
|
||||||
|
func.sum(Transaction.amount).label('total_sales')
|
||||||
|
).filter(Transaction.status == 'Success')
|
||||||
|
|
||||||
|
if machine_ids:
|
||||||
|
query = query.filter(Transaction.machine_id.in_(machine_ids))
|
||||||
|
|
||||||
|
query = query.group_by(extract('year', Transaction.created_at))\
|
||||||
|
.order_by('year')
|
||||||
|
|
||||||
|
results = query.all()
|
||||||
|
|
||||||
|
return [{'year': str(int(year)), 'amount': float(total_sales)}
|
||||||
|
for year, total_sales in results if year]
|
||||||
|
|
||||||
|
|
||||||
|
def get_top_products_by_range(time_range, machine_ids=None, limit=10):
|
||||||
|
"""Get top selling products filtered by time range"""
|
||||||
|
date_filter = get_date_filter(time_range)
|
||||||
|
|
||||||
|
# Base query
|
||||||
|
query = db.session.query(
|
||||||
|
Transaction.product_name,
|
||||||
|
func.sum(Transaction.quantity).label('total_quantity')
|
||||||
|
).filter(Transaction.status == 'Success')
|
||||||
|
|
||||||
|
# Apply machine filter if needed
|
||||||
|
if machine_ids:
|
||||||
|
query = query.filter(Transaction.machine_id.in_(machine_ids))
|
||||||
|
|
||||||
|
# Apply date filter
|
||||||
|
if date_filter:
|
||||||
|
query = query.filter(Transaction.created_at >= date_filter)
|
||||||
|
|
||||||
|
query = query.group_by(Transaction.product_name)\
|
||||||
|
.order_by(func.sum(Transaction.quantity).desc())\
|
||||||
|
.limit(limit)
|
||||||
|
|
||||||
|
results = query.all()
|
||||||
|
|
||||||
|
return [{'product_name': product_name, 'quantity': int(total_quantity)}
|
||||||
|
for product_name, total_quantity in results]
|
||||||
# Additional utility routes for PayU integration
|
# Additional utility routes for PayU integration
|
||||||
|
|
||||||
@bp.route('/verify-payu-hash', methods=['POST'])
|
@bp.route('/verify-payu-hash', methods=['POST'])
|
||||||
@ -1603,7 +2067,7 @@ def get_roles():
|
|||||||
return jsonify({'error': 'Authentication required'}), 401
|
return jsonify({'error': 'Authentication required'}), 401
|
||||||
|
|
||||||
# Only Management and SuperAdmin can view roles
|
# Only Management and SuperAdmin can view roles
|
||||||
if current_user.roles not in ['Management', 'SuperAdmin']:
|
if current_user.roles not in ['Management', 'SuperAdmin','Admin']:
|
||||||
return jsonify({'error': 'Permission denied'}), 403
|
return jsonify({'error': 'Permission denied'}), 403
|
||||||
|
|
||||||
roles = RoleService.get_all_roles()
|
roles = RoleService.get_all_roles()
|
||||||
@ -1622,7 +2086,7 @@ def create_role():
|
|||||||
return jsonify({'error': 'Authentication required'}), 401
|
return jsonify({'error': 'Authentication required'}), 401
|
||||||
|
|
||||||
# Only Management and SuperAdmin can create roles
|
# Only Management and SuperAdmin can create roles
|
||||||
if current_user.roles not in ['Management', 'SuperAdmin']:
|
if current_user.roles not in ['Management', 'SuperAdmin','Admin']:
|
||||||
return jsonify({'error': 'Permission denied'}), 403
|
return jsonify({'error': 'Permission denied'}), 403
|
||||||
|
|
||||||
data = request.json
|
data = request.json
|
||||||
@ -1645,7 +2109,7 @@ def update_role(role_id):
|
|||||||
return jsonify({'error': 'Authentication required'}), 401
|
return jsonify({'error': 'Authentication required'}), 401
|
||||||
|
|
||||||
# Only Management and SuperAdmin can update roles
|
# Only Management and SuperAdmin can update roles
|
||||||
if current_user.roles not in ['Management', 'SuperAdmin']:
|
if current_user.roles not in ['Management', 'SuperAdmin','Admin']:
|
||||||
return jsonify({'error': 'Permission denied'}), 403
|
return jsonify({'error': 'Permission denied'}), 403
|
||||||
|
|
||||||
data = request.json
|
data = request.json
|
||||||
@ -1668,7 +2132,7 @@ def delete_role(role_id):
|
|||||||
return jsonify({'error': 'Authentication required'}), 401
|
return jsonify({'error': 'Authentication required'}), 401
|
||||||
|
|
||||||
# Only Management and SuperAdmin can delete roles
|
# Only Management and SuperAdmin can delete roles
|
||||||
if current_user.roles not in ['Management', 'SuperAdmin']:
|
if current_user.roles not in ['Management', 'SuperAdmin','Admin']:
|
||||||
return jsonify({'error': 'Permission denied'}), 403
|
return jsonify({'error': 'Permission denied'}), 403
|
||||||
|
|
||||||
RoleService.delete_role(role_id)
|
RoleService.delete_role(role_id)
|
||||||
@ -1690,7 +2154,7 @@ def get_available_permissions():
|
|||||||
return jsonify({'error': 'Authentication required'}), 401
|
return jsonify({'error': 'Authentication required'}), 401
|
||||||
|
|
||||||
# Only Management and SuperAdmin can view permissions
|
# Only Management and SuperAdmin can view permissions
|
||||||
if current_user.roles not in ['Management', 'SuperAdmin']:
|
if current_user.roles not in ['Management', 'SuperAdmin','Admin']:
|
||||||
return jsonify({'error': 'Permission denied'}), 403
|
return jsonify({'error': 'Permission denied'}), 403
|
||||||
|
|
||||||
# Define all available permissions
|
# Define all available permissions
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 353 KiB After Width: | Height: | Size: 353 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 2.1 MiB After Width: | Height: | Size: 2.1 MiB |
18
Machine-Backend/reset_password.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from app import create_app, db
|
||||||
|
from app.models.models import User
|
||||||
|
from werkzeug.security import generate_password_hash
|
||||||
|
|
||||||
|
app = create_app()
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
email = "test@example.com"
|
||||||
|
new_password = "test123"
|
||||||
|
|
||||||
|
user = User.query.filter_by(email=email).first()
|
||||||
|
if not user:
|
||||||
|
print("User not found ❌")
|
||||||
|
else:
|
||||||
|
# Hash password in the way your model expects
|
||||||
|
user.password = generate_password_hash(new_password, method='pbkdf2:sha256')
|
||||||
|
db.session.commit()
|
||||||
|
print(f"Password reset successfully for {email}. Login with: {new_password}")
|
||||||
@ -153,7 +153,7 @@ export const appRoutes: Route[] = [
|
|||||||
{
|
{
|
||||||
path: 'role-management',
|
path: 'role-management',
|
||||||
canActivate: [RoleGuard],
|
canActivate: [RoleGuard],
|
||||||
data: { roles: ['Management', 'SuperAdmin'] },
|
data: { roles: ['Management', 'SuperAdmin', 'Admin'] },
|
||||||
loadChildren: () =>import('app/modules/admin/dashboard/role-management/role-management.routes')
|
loadChildren: () =>import('app/modules/admin/dashboard/role-management/role-management.routes')
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -1,162 +1,305 @@
|
|||||||
|
<div class="bg-card flex min-w-0 flex-auto flex-col dark:bg-transparent sm:absolute sm:inset-0 sm:overflow-hidden">
|
||||||
<div class="flex min-w-0 flex-auto flex-col h-full">
|
<div class="flex min-w-0 flex-auto flex-col h-full">
|
||||||
<div class="flex-auto p-6 sm:p-10 overflow-auto max-h-[calc(100vh-100px)]">
|
<div class="flex-auto p-6 sm:p-10 overflow-auto">
|
||||||
<div *ngIf="errorMessage" class="text-red-500 text-center mb-4">
|
<!-- Error Message -->
|
||||||
{{ errorMessage }}
|
<div *ngIf="errorMessage" class="bg-red-50 border-l-4 border-red-500 text-red-700 px-4 py-3 rounded mb-6">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span>{{ errorMessage }}</span>
|
||||||
|
<button (click)="retryFetch()" class="text-red-700 underline hover:text-red-900">Retry</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid w-full min-w-0 grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3">
|
|
||||||
<!-- Machines -->
|
<!-- Top Metrics Row -->
|
||||||
<div class="bg-card flex flex-grow flex-col overflow-hidden rounded-2xl p-6 shadow w-full h-[150px]">
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-6">
|
||||||
<div class="flex items-start justify-between">
|
<!-- Machines Card with Menu -->
|
||||||
<div class="truncate text-lg font-medium leading-6 tracking-tight">
|
<div class="bg-blue-600 text-white rounded-lg p-5 shadow relative">
|
||||||
{{ machineTitle }}
|
<div class="flex items-start justify-between mb-3">
|
||||||
</div>
|
<div class="text-sm font-medium">{{ machineTitle }}</div>
|
||||||
<div class="-mr-3 -mt-2 ml-2">
|
<button mat-icon-button [matMenuTriggerFor]="machineMenu" class="text-white -mt-2 -mr-2">
|
||||||
<button mat-icon-button [matMenuTriggerFor]="summaryMenu">
|
<mat-icon class="text-white text-xl">more_vert</mat-icon>
|
||||||
<mat-icon class="icon-size-5" [svgIcon]="'heroicons_mini:ellipsis-vertical'"></mat-icon>
|
</button>
|
||||||
|
<mat-menu #machineMenu="matMenu">
|
||||||
|
<button mat-menu-item (click)="updateMachine('all')">
|
||||||
|
<mat-icon>list</mat-icon>
|
||||||
|
<span>All Machines</span>
|
||||||
</button>
|
</button>
|
||||||
<mat-menu #summaryMenu="matMenu">
|
<button mat-menu-item (click)="updateMachine('active')">
|
||||||
<button mat-menu-item (click)="updateMachine('active')">
|
<mat-icon>check_circle</mat-icon>
|
||||||
Active Machines
|
<span>Active Machines</span>
|
||||||
</button>
|
</button>
|
||||||
<button mat-menu-item (click)="updateMachine('inactive')">
|
<button mat-menu-item (click)="updateMachine('inactive')">
|
||||||
Inactive Machines
|
<mat-icon>cancel</mat-icon>
|
||||||
</button>
|
<span>Inactive Machines</span>
|
||||||
<button mat-menu-item (click)="updateMachine('all')">
|
</button>
|
||||||
Reset Machines
|
</mat-menu>
|
||||||
</button>
|
|
||||||
</mat-menu>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mt-2 flex flex-col items-center">
|
|
||||||
<div class="text-7xl font-bold leading-none tracking-tight text-blue-500 sm:text-8xl">
|
|
||||||
{{ machineCount }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="text-5xl font-bold">{{ machineCount }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Clients -->
|
<!-- Clients -->
|
||||||
<div class="bg-card flex flex-grow flex-col overflow-hidden rounded-2xl p-6 shadow w-full h-[150px]">
|
<div class="bg-indigo-600 text-white rounded-lg p-5 shadow">
|
||||||
<div class="flex items-start justify-between">
|
<div class="text-sm font-medium mb-3">Clients</div>
|
||||||
<div class="truncate text-lg font-medium leading-6 tracking-tight">
|
<div class="text-5xl font-bold">{{ clients }}</div>
|
||||||
Clients
|
</div>
|
||||||
|
|
||||||
|
<!-- Active Machines -->
|
||||||
|
<div class="bg-cyan-500 text-white rounded-lg p-5 shadow">
|
||||||
|
<div class="text-sm font-medium mb-3">Active Machines</div>
|
||||||
|
<div class="text-5xl font-bold">{{ activeMachines }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Inactive Machines -->
|
||||||
|
<div class="bg-purple-600 text-white rounded-lg p-5 shadow">
|
||||||
|
<div class="text-sm font-medium mb-3">Inactive Machines</div>
|
||||||
|
<div class="text-5xl font-bold">{{ inactiveMachines }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Payment and Bottom Metrics Row -->
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4 mb-6">
|
||||||
|
<!-- Payment Overview - Takes 1 column -->
|
||||||
|
<div class="bg-orange-500 text-white rounded-lg p-5 shadow">
|
||||||
|
<div class="text-sm font-semibold mb-4 uppercase tracking-wide">Payment Overview</div>
|
||||||
|
<div class="space-y-2 mb-4">
|
||||||
|
<div class="flex justify-between items-center text-sm">
|
||||||
|
<span>Cash</span>
|
||||||
|
<span class="font-semibold">₹{{ paymentCash | number:'1.0-0' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center text-sm">
|
||||||
|
<span>Cashless</span>
|
||||||
|
<span class="font-semibold">₹{{ paymentCashless | number:'1.0-0' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center text-sm">
|
||||||
|
<span class="text-xs">UPI/Card (PhonePe)</span>
|
||||||
|
<span class="font-semibold">₹{{ paymentUPIWalletCard | number:'1.0-0' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center text-sm">
|
||||||
|
<span class="text-xs">UPI/Wallet (Paytm)</span>
|
||||||
|
<span class="font-semibold">₹{{ paymentUPIWalletPaytm | number:'1.0-0' }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 flex flex-col items-center">
|
<div class="border-t border-white/30 pt-3 space-y-1">
|
||||||
<div class="text-7xl font-bold leading-none tracking-tight text-red-500 sm:text-8xl">
|
<div class="flex justify-between text-xs">
|
||||||
{{ clients }}
|
<span>Refund</span>
|
||||||
|
<span>₹{{ refund | number:'1.2-2' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between text-xs">
|
||||||
|
<span>Refund Processed</span>
|
||||||
|
<span>₹{{ refundProcessed | number:'1.2-2' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between text-base font-bold pt-2 border-t border-white/30">
|
||||||
|
<span>Total</span>
|
||||||
|
<span>₹{{ paymentTotal | number:'1.0-0' }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Company Users -->
|
|
||||||
<div class="bg-card flex flex-grow flex-col overflow-hidden rounded-2xl p-6 shadow w-full h-[150px]">
|
<!-- Other Metrics - Takes 2 columns -->
|
||||||
<div class="flex items-start justify-between">
|
<div class="lg:col-span-2 grid grid-cols-2 sm:grid-cols-4 gap-4">
|
||||||
<div class="truncate text-lg font-medium leading-6 tracking-tight">
|
<!-- Company Users -->
|
||||||
Company Users
|
<div class="bg-teal-600 text-white rounded-lg p-5 shadow">
|
||||||
</div>
|
<div class="text-sm font-medium mb-3">Company Users</div>
|
||||||
|
<div class="text-5xl font-bold">{{ companyUsers }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 flex flex-col items-center">
|
|
||||||
<div class="text-7xl font-bold leading-none tracking-tight text-amber-500 sm:text-8xl">
|
<!-- Client Users -->
|
||||||
{{ companyUsers }}
|
<div class="bg-slate-600 text-white rounded-lg p-5 shadow">
|
||||||
|
<div class="text-sm font-medium mb-3">Client Users</div>
|
||||||
|
<div class="text-5xl font-bold">{{ clientUsers }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Transactions -->
|
||||||
|
<div class="bg-green-700 text-white rounded-lg p-5 shadow">
|
||||||
|
<div class="text-sm font-medium mb-3">Transactions</div>
|
||||||
|
<div class="text-5xl font-bold">{{ transactions | number }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sales -->
|
||||||
|
<div class="bg-green-600 text-white rounded-lg p-5 shadow">
|
||||||
|
<div class="text-sm font-medium mb-3">Total Sales</div>
|
||||||
|
<div class="text-4xl font-bold">{{ sales }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Machine Status Panels -->
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-6">
|
||||||
|
<!-- Machine Operation Status -->
|
||||||
|
<div class="bg-white rounded-lg shadow p-6">
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<h3 class="text-base font-semibold text-gray-900">Machine Operation Status</h3>
|
||||||
|
<mat-icon class="text-blue-600 cursor-pointer">filter_list</mat-icon>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-x-6 gap-y-3">
|
||||||
|
<div *ngFor="let key of getOperationStatusKeys()" class="flex justify-between items-center">
|
||||||
|
<span class="text-sm text-gray-700">{{ key }}</span>
|
||||||
|
<span class="text-lg font-semibold text-gray-900">{{ machineOperationStatus[key] }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Client User -->
|
|
||||||
<div class="bg-card flex flex-grow flex-col overflow-hidden rounded-2xl p-6 shadow w-full h-[150px]">
|
<!-- Machine Stock Status -->
|
||||||
<div class="flex items-start justify-between">
|
<div class="bg-white rounded-lg shadow p-6">
|
||||||
<div class="truncate text-lg font-medium leading-6 tracking-tight">
|
<div class="flex items-center justify-between mb-4">
|
||||||
Client User
|
<h3 class="text-base font-semibold text-gray-900">Machine Stock Status</h3>
|
||||||
</div>
|
<mat-icon class="text-blue-600 cursor-pointer">filter_list</mat-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 flex flex-col items-center">
|
<div class="grid grid-cols-5 gap-2">
|
||||||
<div class="text-7xl font-bold leading-none tracking-tight text-red-500 sm:text-8xl">
|
<div *ngFor="let key of getStockStatusKeys()"
|
||||||
{{ clientUsers }}
|
class="text-center p-3 rounded-lg"
|
||||||
</div>
|
[ngClass]="{
|
||||||
</div>
|
'bg-green-100 text-green-800': key === '75 - 100%',
|
||||||
</div>
|
'bg-yellow-100 text-yellow-800': key === '50 - 75%',
|
||||||
<!-- Transactions -->
|
'bg-orange-100 text-orange-800': key === '25 - 50%',
|
||||||
<div class="bg-card flex flex-grow flex-col overflow-hidden rounded-2xl p-6 shadow w-full h-[150px]">
|
'bg-red-100 text-red-800': key === '0 - 25%',
|
||||||
<div class="flex items-start justify-between">
|
'bg-pink-100 text-pink-800': key === 'N/A'
|
||||||
<div class="truncate text-lg font-medium leading-6 tracking-tight">
|
}">
|
||||||
Transactions
|
<div class="text-[10px] font-medium mb-1">{{ key }}</div>
|
||||||
</div>
|
<div class="text-2xl font-bold">{{ machineStockStatus[key] }}</div>
|
||||||
</div>
|
|
||||||
<div class="mt-2 flex flex-col items-center">
|
|
||||||
<div class="text-7xl font-bold leading-none tracking-tight text-red-500 sm:text-8xl">
|
|
||||||
{{ transactions }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Sales -->
|
|
||||||
<div class="bg-card flex flex-grow flex-col overflow-hidden rounded-2xl p-6 shadow w-full h-[150px]">
|
|
||||||
<div class="flex items-start justify-between">
|
|
||||||
<div class="truncate text-lg font-medium leading-6 tracking-tight">
|
|
||||||
Sales
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mt-2 flex flex-col items-center">
|
|
||||||
<div class="text-7xl font-bold leading-none tracking-tight text-green-500 sm:text-8xl">
|
|
||||||
{{ sales }}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Chart Visualizations -->
|
|
||||||
<div class="grid w-full min-w-0 grid-cols-1 gap-6 sm:grid-cols-1 md:grid-cols-2 mt-6">
|
<!-- Charts Section -->
|
||||||
<!-- Bar Chart for Key Metrics -->
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-6">
|
||||||
<div class="bg-card flex flex-grow flex-col overflow-hidden rounded-2xl p-6 shadow w-full">
|
<!-- Product Sales Chart -->
|
||||||
<div class="text-lg font-medium leading-6 tracking-tight mb-4">
|
<div class="bg-white rounded-lg shadow p-6">
|
||||||
Key Metrics Overview
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<h3 class="text-base font-semibold text-gray-900">Product Sales</h3>
|
||||||
|
<button mat-stroked-button [matMenuTriggerFor]="productSalesMenu" class="text-sm border border-blue-300 text-blue-700 rounded-lg px-4 py-2 hover:bg-blue-50 flex items-center gap-2">
|
||||||
|
<mat-icon class="text-lg">schedule</mat-icon>
|
||||||
|
<span class="capitalize font-medium">{{ productSalesTimeRange }}</span>
|
||||||
|
<mat-icon class="text-lg">expand_more</mat-icon>
|
||||||
|
</button>
|
||||||
|
<mat-menu #productSalesMenu="matMenu">
|
||||||
|
<button mat-menu-item (click)="updateProductSalesTimeRange('day')">
|
||||||
|
<mat-icon>today</mat-icon>
|
||||||
|
<span>Daily</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="updateProductSalesTimeRange('week')">
|
||||||
|
<mat-icon>date_range</mat-icon>
|
||||||
|
<span>Weekly</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="updateProductSalesTimeRange('month')">
|
||||||
|
<mat-icon>calendar_month</mat-icon>
|
||||||
|
<span>Monthly</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="updateProductSalesTimeRange('year')">
|
||||||
|
<mat-icon>calendar_today</mat-icon>
|
||||||
|
<span>Yearly</span>
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
||||||
</div>
|
</div>
|
||||||
<apx-chart
|
<apx-chart
|
||||||
[series]="barChartOptions.series"
|
[series]="productSalesChartOptions.series"
|
||||||
[chart]="barChartOptions.chart"
|
[chart]="productSalesChartOptions.chart"
|
||||||
[xaxis]="barChartOptions.xaxis"
|
[xaxis]="productSalesChartOptions.xaxis"
|
||||||
[colors]="barChartOptions.colors"
|
[yaxis]="productSalesChartOptions.yaxis"
|
||||||
[plotOptions]="barChartOptions.plotOptions"
|
[colors]="productSalesChartOptions.colors"
|
||||||
[dataLabels]="barChartOptions.dataLabels"
|
[plotOptions]="productSalesChartOptions.plotOptions"
|
||||||
[tooltip]="barChartOptions.tooltip"
|
[dataLabels]="productSalesChartOptions.dataLabels"
|
||||||
|
[fill]="productSalesChartOptions.fill"
|
||||||
|
[legend]="productSalesChartOptions.legend"
|
||||||
|
[grid]="productSalesChartOptions.grid"
|
||||||
></apx-chart>
|
></apx-chart>
|
||||||
</div>
|
</div>
|
||||||
<!-- Line Chart for Trends -->
|
|
||||||
<div class="bg-card flex flex-grow flex-col overflow-hidden rounded-2xl p-6 shadow w-full">
|
<!-- Top Selling Products -->
|
||||||
<div class="text-lg font-medium leading-6 tracking-tight mb-4">
|
<div class="bg-white rounded-lg shadow p-6">
|
||||||
Transaction Trends
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<h3 class="text-base font-semibold text-gray-900">Top Selling Products</h3>
|
||||||
|
<button mat-stroked-button [matMenuTriggerFor]="topProductsMenu" class="text-sm border border-purple-300 text-purple-700 rounded-lg px-4 py-2 hover:bg-purple-50 flex items-center gap-2">
|
||||||
|
<mat-icon class="text-lg">schedule</mat-icon>
|
||||||
|
<span class="capitalize font-medium">{{ topProductsTimeRange }}</span>
|
||||||
|
<mat-icon class="text-lg">expand_more</mat-icon>
|
||||||
|
</button>
|
||||||
|
<mat-menu #topProductsMenu="matMenu">
|
||||||
|
<button mat-menu-item (click)="updateTopProductsTimeRange('day')">
|
||||||
|
<mat-icon>today</mat-icon>
|
||||||
|
<span>Daily</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="updateTopProductsTimeRange('week')">
|
||||||
|
<mat-icon>date_range</mat-icon>
|
||||||
|
<span>Weekly</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="updateTopProductsTimeRange('month')">
|
||||||
|
<mat-icon>calendar_month</mat-icon>
|
||||||
|
<span>Monthly</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="updateTopProductsTimeRange('year')">
|
||||||
|
<mat-icon>calendar_today</mat-icon>
|
||||||
|
<span>Yearly</span>
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
||||||
</div>
|
</div>
|
||||||
<apx-chart
|
<div class="overflow-auto max-h-[320px]">
|
||||||
[series]="lineChartOptions.series"
|
<table class="w-full">
|
||||||
[chart]="lineChartOptions.chart"
|
<thead class="bg-teal-600 text-white sticky top-0">
|
||||||
[xaxis]="lineChartOptions.xaxis"
|
<tr>
|
||||||
[yaxis]="lineChartOptions.yaxis"
|
<th class="text-left p-3 text-xs font-semibold">Product</th>
|
||||||
[colors]="lineChartOptions.colors"
|
<th class="text-right p-3 text-xs font-semibold">Quantity</th>
|
||||||
[stroke]="lineChartOptions.stroke"
|
</tr>
|
||||||
[tooltip]="lineChartOptions.tooltip"
|
</thead>
|
||||||
></apx-chart>
|
<tbody>
|
||||||
</div>
|
<tr *ngFor="let product of topSellingProducts; let i = index"
|
||||||
<!-- Doughnut Chart for Sales Distribution -->
|
class="border-b border-gray-100 hover:bg-gray-50 transition-colors"
|
||||||
<div class="bg-card flex flex-grow flex-col overflow-hidden rounded-2xl p-6 shadow w-full">
|
[ngClass]="{'bg-gray-50': i % 2 === 0}">
|
||||||
<div class="text-lg font-medium leading-6 tracking-tight mb-4">
|
<td class="p-3 text-sm text-gray-700">{{ product.product_name }}</td>
|
||||||
Sales Distribution
|
<td class="p-3 text-sm text-right font-semibold text-gray-900">{{ product.quantity | number }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngIf="topSellingProducts.length === 0">
|
||||||
|
<td colspan="2" class="p-6 text-center text-gray-500">
|
||||||
|
<mat-icon class="text-4xl text-gray-300 mb-2">inbox</mat-icon>
|
||||||
|
<div class="text-sm">No data available</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<apx-chart
|
|
||||||
[series]="doughnutChartOptions.series"
|
|
||||||
[chart]="doughnutChartOptions.chart"
|
|
||||||
[labels]="doughnutChartOptions.labels"
|
|
||||||
[colors]="doughnutChartOptions.colors"
|
|
||||||
[responsive]="doughnutChartOptions.responsive"
|
|
||||||
></apx-chart>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- Pie Chart for Client User Categories -->
|
</div>
|
||||||
<div class="bg-card flex flex-grow flex-col overflow-hidden rounded-2xl p-6 shadow w-full">
|
|
||||||
<div class="text-lg font-medium leading-6 tracking-tight mb-4">
|
<!-- Sales Area Chart -->
|
||||||
Client User Categories
|
<div class="bg-white rounded-lg shadow p-6 mb-6">
|
||||||
</div>
|
<div class="flex items-center justify-between mb-4">
|
||||||
<apx-chart
|
<h3 class="text-base font-semibold text-gray-900">Sales Trend</h3>
|
||||||
[series]="pieChartOptions.series"
|
<button mat-stroked-button [matMenuTriggerFor]="salesMenu" class="text-sm border border-green-300 text-green-700 rounded-lg px-4 py-2 hover:bg-green-50 flex items-center gap-2">
|
||||||
[chart]="pieChartOptions.chart"
|
<mat-icon class="text-lg">schedule</mat-icon>
|
||||||
[labels]="pieChartOptions.labels"
|
<span class="capitalize font-medium">{{ salesTimeRange }}</span>
|
||||||
[colors]="pieChartOptions.colors"
|
<mat-icon class="text-lg">expand_more</mat-icon>
|
||||||
[responsive]="pieChartOptions.responsive"
|
</button>
|
||||||
></apx-chart>
|
<mat-menu #salesMenu="matMenu">
|
||||||
|
<button mat-menu-item (click)="updateSalesTimeRange('day')">
|
||||||
|
<mat-icon>today</mat-icon>
|
||||||
|
<span>Daily</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="updateSalesTimeRange('week')">
|
||||||
|
<mat-icon>date_range</mat-icon>
|
||||||
|
<span>Weekly</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="updateSalesTimeRange('month')">
|
||||||
|
<mat-icon>calendar_month</mat-icon>
|
||||||
|
<span>Monthly</span>
|
||||||
|
</button>
|
||||||
|
<button mat-menu-item (click)="updateSalesTimeRange('year')">
|
||||||
|
<mat-icon>calendar_today</mat-icon>
|
||||||
|
<span>Yearly</span>
|
||||||
|
</button>
|
||||||
|
</mat-menu>
|
||||||
</div>
|
</div>
|
||||||
|
<apx-chart
|
||||||
|
[series]="salesChartOptions.series"
|
||||||
|
[chart]="salesChartOptions.chart"
|
||||||
|
[xaxis]="salesChartOptions.xaxis"
|
||||||
|
[yaxis]="salesChartOptions.yaxis"
|
||||||
|
[colors]="salesChartOptions.colors"
|
||||||
|
[stroke]="salesChartOptions.stroke"
|
||||||
|
[dataLabels]="salesChartOptions.dataLabels"
|
||||||
|
[fill]="salesChartOptions.fill"
|
||||||
|
[grid]="salesChartOptions.grid"
|
||||||
|
></apx-chart>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -23,10 +23,23 @@ interface DashboardMetrics {
|
|||||||
client_users: number;
|
client_users: number;
|
||||||
transactions: number;
|
transactions: number;
|
||||||
sales: string;
|
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;
|
error?: string;
|
||||||
transaction_trends?: { date: string; transactions: number }[];
|
|
||||||
sales_distribution?: { [key: string]: number };
|
|
||||||
client_user_categories?: { [key: string]: number };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -56,112 +69,41 @@ export class ExampleComponent implements OnInit {
|
|||||||
clientUsers: number = 0;
|
clientUsers: number = 0;
|
||||||
transactions: number = 0;
|
transactions: number = 0;
|
||||||
sales: string = '₹0.00';
|
sales: string = '₹0.00';
|
||||||
|
activeMachines: number = 0;
|
||||||
|
inactiveMachines: number = 0;
|
||||||
errorMessage: string = '';
|
errorMessage: string = '';
|
||||||
loading: boolean = false;
|
loading: boolean = false;
|
||||||
barChartOptions: Partial<ApexOptions>;
|
|
||||||
lineChartOptions: Partial<ApexOptions>;
|
// Payment breakdown
|
||||||
doughnutChartOptions: Partial<ApexOptions>;
|
paymentCash: number = 0;
|
||||||
pieChartOptions: Partial<ApexOptions>;
|
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<ApexOptions>;
|
||||||
|
salesChartOptions: Partial<ApexOptions>;
|
||||||
|
|
||||||
private readonly baseUrl = environment.apiUrl || 'http://localhost:5000';
|
private readonly baseUrl = environment.apiUrl || 'http://localhost:5000';
|
||||||
|
|
||||||
constructor(private http: HttpClient) {
|
constructor(private http: HttpClient) {
|
||||||
// Initialize bar chart options with mock data
|
this.initializeCharts();
|
||||||
this.barChartOptions = {
|
|
||||||
series: [{
|
|
||||||
name: 'Metrics',
|
|
||||||
data: [150, 80, 120, 60]
|
|
||||||
}],
|
|
||||||
chart: {
|
|
||||||
type: 'bar',
|
|
||||||
height: 350
|
|
||||||
},
|
|
||||||
plotOptions: {
|
|
||||||
bar: {
|
|
||||||
horizontal: false,
|
|
||||||
columnWidth: '55%'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dataLabels: {
|
|
||||||
enabled: false
|
|
||||||
},
|
|
||||||
xaxis: {
|
|
||||||
categories: ['Machines', 'Clients', 'Company Users', 'Client Users']
|
|
||||||
},
|
|
||||||
colors: ['#3B82F6', '#EF4444', '#F59E0B', '#EF4444'],
|
|
||||||
tooltip: {
|
|
||||||
y: {
|
|
||||||
formatter: (val) => `${val}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initialize line chart options with mock data
|
|
||||||
this.lineChartOptions = {
|
|
||||||
series: [{
|
|
||||||
name: 'Transactions',
|
|
||||||
data: [10, 15, 8, 12, 20]
|
|
||||||
}],
|
|
||||||
chart: {
|
|
||||||
type: 'line',
|
|
||||||
height: 350
|
|
||||||
},
|
|
||||||
stroke: {
|
|
||||||
width: 3,
|
|
||||||
curve: 'smooth'
|
|
||||||
},
|
|
||||||
xaxis: {
|
|
||||||
type: 'category',
|
|
||||||
categories: ['2025-08-15', '2025-08-16', '2025-08-17', '2025-08-18', '2025-08-19']
|
|
||||||
},
|
|
||||||
yaxis: {
|
|
||||||
title: {
|
|
||||||
text: 'Transactions'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
colors: ['#EF4444'],
|
|
||||||
tooltip: {
|
|
||||||
x: {
|
|
||||||
format: 'dd MMM yyyy'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initialize doughnut chart options with mock data
|
|
||||||
this.doughnutChartOptions = {
|
|
||||||
series: [40, 30, 20],
|
|
||||||
chart: {
|
|
||||||
type: 'donut',
|
|
||||||
height: 350
|
|
||||||
},
|
|
||||||
labels: ['Product A', 'Product B', 'Product C'],
|
|
||||||
colors: ['#10B981', '#F59E0B', '#8B5CF6'],
|
|
||||||
responsive: [{
|
|
||||||
breakpoint: 480,
|
|
||||||
options: {
|
|
||||||
chart: { width: 200 },
|
|
||||||
legend: { position: 'bottom' }
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initialize pie chart options with mock data
|
|
||||||
this.pieChartOptions = {
|
|
||||||
series: [50, 30, 20],
|
|
||||||
chart: {
|
|
||||||
type: 'pie',
|
|
||||||
height: 350
|
|
||||||
},
|
|
||||||
labels: ['Premium', 'Standard', 'Basic'],
|
|
||||||
colors: ['#EF4444', '#6B7280', '#34D399'],
|
|
||||||
responsive: [{
|
|
||||||
breakpoint: 480,
|
|
||||||
options: {
|
|
||||||
chart: { width: 200 },
|
|
||||||
legend: { position: 'bottom' }
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@ -169,35 +111,259 @@ export class ExampleComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateMachine(filter: 'active' | 'inactive' | 'all') {
|
updateMachine(filter: 'active' | 'inactive' | 'all') {
|
||||||
|
console.log('Filter clicked:', filter);
|
||||||
this.fetchDashboardMetrics(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) {
|
private fetchDashboardMetrics(filter: string) {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.machineTitle = 'Loading...';
|
this.machineTitle = 'Loading...';
|
||||||
this.errorMessage = '';
|
this.errorMessage = '';
|
||||||
|
|
||||||
const url = `${this.baseUrl}/dashboard-metrics?machine_filter=${filter}`;
|
const url = `${this.baseUrl}/dashboard-metrics?machine_filter=${filter}`;
|
||||||
console.log('Fetching from URL:', url);
|
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<DashboardMetrics>(url, {
|
this.http.get<DashboardMetrics>(url, {
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': token ? `Bearer ${token}` : ''
|
||||||
}
|
}
|
||||||
}).subscribe({
|
}).subscribe({
|
||||||
next: (response) => {
|
next: (response) => {
|
||||||
console.log('API Response:', response);
|
console.log('✓ Dashboard API Response:', response);
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
|
||||||
if (response.error) {
|
if (response.error) {
|
||||||
this.handleError(`Server Error: ${response.error}`);
|
this.handleError(`Server Error: ${response.error}`);
|
||||||
} else {
|
} else {
|
||||||
this.updateDashboardData(response);
|
this.updateDashboardData(response);
|
||||||
|
console.log('✓ Dashboard data updated successfully');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: (error: HttpErrorResponse) => {
|
error: (error: HttpErrorResponse) => {
|
||||||
console.error('HTTP Error:', error);
|
console.error('✗ Dashboard HTTP Error:', error);
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.handleHttpError(error);
|
this.handleHttpError(error);
|
||||||
}
|
}
|
||||||
@ -205,6 +371,7 @@ export class ExampleComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private updateDashboardData(response: DashboardMetrics) {
|
private updateDashboardData(response: DashboardMetrics) {
|
||||||
|
// Update basic metrics
|
||||||
this.machineTitle = response.machine_title || 'Machines';
|
this.machineTitle = response.machine_title || 'Machines';
|
||||||
this.machineCount = response.machine_count || 0;
|
this.machineCount = response.machine_count || 0;
|
||||||
this.clients = response.clients || 0;
|
this.clients = response.clients || 0;
|
||||||
@ -212,54 +379,73 @@ export class ExampleComponent implements OnInit {
|
|||||||
this.clientUsers = response.client_users || 0;
|
this.clientUsers = response.client_users || 0;
|
||||||
this.transactions = response.transactions || 0;
|
this.transactions = response.transactions || 0;
|
||||||
this.sales = `₹${response.sales || '0.00'}`;
|
this.sales = `₹${response.sales || '0.00'}`;
|
||||||
|
this.activeMachines = response.active_machines || 0;
|
||||||
|
this.inactiveMachines = response.inactive_machines || 0;
|
||||||
this.errorMessage = '';
|
this.errorMessage = '';
|
||||||
|
|
||||||
// Update bar chart
|
// Update payment breakdown
|
||||||
this.barChartOptions = {
|
if (response.payment_breakdown) {
|
||||||
...this.barChartOptions,
|
this.paymentCash = response.payment_breakdown.cash || 0;
|
||||||
series: [{
|
this.paymentCashless = response.payment_breakdown.cashless || 0;
|
||||||
name: 'Metrics',
|
this.paymentUPIWalletCard = response.payment_breakdown.upi_wallet_card || 0;
|
||||||
data: [
|
this.paymentUPIWalletPaytm = response.payment_breakdown.upi_wallet_paytm || 0;
|
||||||
this.machineCount,
|
this.refund = response.payment_breakdown.refund || 0;
|
||||||
this.clients,
|
this.refundProcessed = response.payment_breakdown.refund_processed || 0;
|
||||||
this.companyUsers,
|
this.paymentTotal = response.payment_breakdown.total || 0;
|
||||||
this.clientUsers
|
}
|
||||||
]
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update line chart with transaction trends
|
// Update machine operation status
|
||||||
if (response.transaction_trends) {
|
if (response.machine_operation_status) {
|
||||||
this.lineChartOptions = {
|
this.machineOperationStatus = response.machine_operation_status;
|
||||||
...this.lineChartOptions,
|
}
|
||||||
|
|
||||||
|
// 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: [{
|
series: [{
|
||||||
name: 'Transactions',
|
name: 'Sales',
|
||||||
data: response.transaction_trends.map(item => item.transactions)
|
data: response.product_sales_yearly.map(item => item.amount)
|
||||||
}],
|
}],
|
||||||
xaxis: {
|
xaxis: {
|
||||||
...this.lineChartOptions.xaxis,
|
...this.productSalesChartOptions.xaxis,
|
||||||
categories: response.transaction_trends.map(item => item.date)
|
categories: response.product_sales_yearly.map(item => item.year)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update doughnut chart with sales distribution
|
// Update Sales Chart
|
||||||
if (response.sales_distribution) {
|
if (response.sales_yearly && response.sales_yearly.length > 0) {
|
||||||
this.doughnutChartOptions = {
|
this.salesChartOptions = {
|
||||||
...this.doughnutChartOptions,
|
...this.salesChartOptions,
|
||||||
series: Object.values(response.sales_distribution),
|
series: [{
|
||||||
labels: Object.keys(response.sales_distribution)
|
name: 'Sales',
|
||||||
|
data: response.sales_yearly.map(item => item.amount)
|
||||||
|
}],
|
||||||
|
xaxis: {
|
||||||
|
...this.salesChartOptions.xaxis,
|
||||||
|
categories: response.sales_yearly.map(item => item.year)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update pie chart with client user categories
|
getStockStatusKeys(): string[] {
|
||||||
if (response.client_user_categories) {
|
return Object.keys(this.machineStockStatus).sort();
|
||||||
this.pieChartOptions = {
|
}
|
||||||
...this.pieChartOptions,
|
|
||||||
series: Object.values(response.client_user_categories),
|
getOperationStatusKeys(): string[] {
|
||||||
labels: Object.keys(response.client_user_categories)
|
return Object.keys(this.machineOperationStatus).sort();
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleError(message: string) {
|
private handleError(message: string) {
|
||||||
@ -272,18 +458,15 @@ export class ExampleComponent implements OnInit {
|
|||||||
|
|
||||||
if (error.status === 0) {
|
if (error.status === 0) {
|
||||||
errorMessage += 'Cannot connect to server. Please check if the Flask server is running on the correct port.';
|
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) {
|
} else if (error.status === 404) {
|
||||||
errorMessage += 'API endpoint not found. Please check the server routing.';
|
errorMessage += 'API endpoint not found. Please check the server routing.';
|
||||||
} else if (error.status >= 500) {
|
} else if (error.status >= 500) {
|
||||||
errorMessage += 'Internal server error. Please check the server logs.';
|
errorMessage += 'Internal server error. Please check the server logs.';
|
||||||
} else {
|
} else {
|
||||||
errorMessage += `Status: ${error.status} - ${error.statusText}. `;
|
errorMessage += `Status: ${error.status} - ${error.statusText}. `;
|
||||||
|
errorMessage += `Details: ${error.error?.error || error.message || 'Unknown error'}`;
|
||||||
if (error.error && typeof error.error === 'string' && error.error.includes('<!doctype')) {
|
|
||||||
errorMessage += 'Server returned HTML instead of JSON. Check your proxy configuration.';
|
|
||||||
} else {
|
|
||||||
errorMessage += `Details: ${error.error?.error || error.message || 'Unknown error'}`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.handleError(errorMessage);
|
this.handleError(errorMessage);
|
||||||
@ -297,39 +480,18 @@ export class ExampleComponent implements OnInit {
|
|||||||
this.clientUsers = 0;
|
this.clientUsers = 0;
|
||||||
this.transactions = 0;
|
this.transactions = 0;
|
||||||
this.sales = '₹0.00';
|
this.sales = '₹0.00';
|
||||||
|
this.activeMachines = 0;
|
||||||
// Reset chart data
|
this.inactiveMachines = 0;
|
||||||
this.barChartOptions = {
|
this.paymentCash = 0;
|
||||||
...this.barChartOptions,
|
this.paymentCashless = 0;
|
||||||
series: [{
|
this.paymentUPIWalletCard = 0;
|
||||||
name: 'Metrics',
|
this.paymentUPIWalletPaytm = 0;
|
||||||
data: [0, 0, 0, 0]
|
this.refund = 0;
|
||||||
}]
|
this.refundProcessed = 0;
|
||||||
};
|
this.paymentTotal = 0;
|
||||||
|
this.machineOperationStatus = {};
|
||||||
this.lineChartOptions = {
|
this.machineStockStatus = {};
|
||||||
...this.lineChartOptions,
|
this.topSellingProducts = [];
|
||||||
series: [{
|
|
||||||
name: 'Transactions',
|
|
||||||
data: []
|
|
||||||
}],
|
|
||||||
xaxis: {
|
|
||||||
...this.lineChartOptions.xaxis,
|
|
||||||
categories: []
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.doughnutChartOptions = {
|
|
||||||
...this.doughnutChartOptions,
|
|
||||||
series: [],
|
|
||||||
labels: []
|
|
||||||
};
|
|
||||||
|
|
||||||
this.pieChartOptions = {
|
|
||||||
...this.pieChartOptions,
|
|
||||||
series: [],
|
|
||||||
labels: []
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
retryFetch() {
|
retryFetch() {
|
||||||
|
|||||||
@ -29,14 +29,14 @@
|
|||||||
|
|
||||||
<body class="mat-typography">
|
<body class="mat-typography">
|
||||||
<!-- Splash screen -->
|
<!-- Splash screen -->
|
||||||
<fuse-splash-screen>
|
<!-- <fuse-splash-screen>
|
||||||
<img src="images/logo/logo.svg" alt="Fuse logo" />
|
<img src="images/logo/logo.svg" alt="Fuse logo" />
|
||||||
<div class="spinner">
|
<div class="spinner">
|
||||||
<div class="bounce1"></div>
|
<div class="bounce1"></div>
|
||||||
<div class="bounce2"></div>
|
<div class="bounce2"></div>
|
||||||
<div class="bounce3"></div>
|
<div class="bounce3"></div>
|
||||||
</div>
|
</div>
|
||||||
</fuse-splash-screen>
|
</fuse-splash-screen> -->
|
||||||
|
|
||||||
<!-- App root -->
|
<!-- App root -->
|
||||||
<app-root></app-root>
|
<app-root></app-root>
|
||||||
|
|||||||