279 lines
13 KiB
HTML
279 lines
13 KiB
HTML
{% extends 'base.html' %}
|
|
{% load i18n %}
|
|
{% block title %}
|
|
{% trans "Sales Dashboard" %}
|
|
{% endblock %}
|
|
{% block content %}
|
|
<div class="main-content flex-grow-1 container-fluid mt-4 mb-3">
|
|
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center mb-5 pb-3 border-bottom">
|
|
<h2 class="h3 fw-bold mb-3 mb-md-0">
|
|
{% trans "Sales Dashboard" %} <i class="fas fa-chart-area text-primary ms-2"></i>
|
|
</h2>
|
|
<form method="GET" class="date-filter-form">
|
|
<div class="row g-3">
|
|
<div class="col-12 col-md-4">
|
|
<label for="start-date" class="form-label">{% trans "Start Date" %}</label>
|
|
<input type="date"
|
|
class="form-control"
|
|
id="start-date"
|
|
name="start_date"
|
|
value="{{ start_date|date:'Y-m-d' }}"
|
|
required>
|
|
</div>
|
|
<div class="col-12 col-md-4">
|
|
<label for="end-date" class="form-label">{% trans "End Date" %}</label>
|
|
<input type="date"
|
|
class="form-control"
|
|
id="end-date"
|
|
name="end_date"
|
|
value="{{ end_date|date:'Y-m-d' }}"
|
|
required>
|
|
</div>
|
|
<div class="col-12 col-md-4 d-flex align-items-end">
|
|
<button type="submit" class="btn btn-primary w-100">{% trans "Apply Filter" %}</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="row g-4 mb-5">
|
|
<h3 class="fw-bold mb-3">{% trans "Inventory KPIs" %}</h3>
|
|
<div class="col-sm-6 col-md-4 col-lg-3">
|
|
<div class="card h-100 shadow-sm border-0">
|
|
<div class="card-body p-4">
|
|
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Total Cars in Inventory" %}</p>
|
|
<h4 class="fw-bolder text-primary mb-3">{{ total_cars_in_inventory }}</h4>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-6 col-md-4 col-lg-3">
|
|
<div class="card h-100 shadow-sm border-0">
|
|
<div class="card-body p-4">
|
|
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "New Cars in Inventory" %}</p>
|
|
<h4 class="fw-bolder text-primary mb-3">{{ total_new_cars_in_inventory }}</h4>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-6 col-md-4 col-lg-3">
|
|
<div class="card h-100 shadow-sm border-0">
|
|
<div class="card-body p-4">
|
|
<p class="text-uppercase text-muted fw-bold small mb-1">{% trans "Used Cars in Inventory" %}</p>
|
|
<h4 class="fw-bolder text-primary mb-3">{{ total_used_cars_in_inventory }}</h4>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-6 col-md-4 col-lg-3">
|
|
<div class="card h-100 shadow-sm border-0">
|
|
<div class="card-body p-4">
|
|
<p class="text-uppercase text-danger fw-bold small mb-1">
|
|
<a class="text-danger"
|
|
href="{% url 'aging_inventory_list' request.dealer.slug %}">{% trans "Aging Inventory (> 60 days)" %}</a>
|
|
</p>
|
|
<h4 class="fw-bolder text-danger mb-3">
|
|
<a class="text-danger"
|
|
href="{% url 'aging_inventory_list' request.dealer.slug %}">{{ aging_inventory_count }}</a>
|
|
</h4>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row g-4 mb-5">
|
|
<div class="col-md-6">
|
|
<div class="card h-100 shadow-sm border-0">
|
|
<div class="card-header bg-white border-bottom-0">
|
|
<h5 class="fw-bold mb-0 text-dark">{% trans "Top Lead Sources" %} {% trans "Total Leads: " %}{{total_leads}}</h5>
|
|
</div>
|
|
<div class="card-body d-flex align-items-center justify-content-center"
|
|
style="height: 400px">
|
|
<canvas id="leadSourcesChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="card h-100 shadow-sm border-0">
|
|
<div class="card-header bg-white border-bottom-0">
|
|
<h5 class="fw-bold mb-0 text-dark">{% trans "Lead Conversion Funnel" %}</h5>
|
|
</div>
|
|
<div class="card-body d-flex align-items-center justify-content-center"
|
|
style="height: 400px">
|
|
<canvas id="leadFunnelChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
{% endblock content %}
|
|
{% block customJS %}
|
|
<script>
|
|
// Define your color palette at the top
|
|
const primaryColor = '#7249b6'; // A vibrant purple
|
|
const secondaryColor = '#8193a6'; // A muted gray/blue
|
|
const successColor = '#00d074'; // A bright green
|
|
const dangerColor = '#e63757'; // A deep red
|
|
const infoColor = '#17a2b8'; // Correcting the missing variable
|
|
const warningColor = '#ffc107'; // Add other colors if needed
|
|
|
|
const chartColors = [
|
|
'#7249b6', '#00d074', '#e63757', '#17a2b8', '#ffc107',
|
|
'#8193a6', '#28a745', '#6c757d', '#fd7e14', '#dc3545',
|
|
'#20c997', '#6f42c1', '#e83e8c', '#6610f2', '#007bff',
|
|
'#495057'
|
|
];
|
|
|
|
// Pass translated strings from Django to JavaScript
|
|
const translatedStrings = {
|
|
numberOfLeads: "{% trans 'Number of Leads' %}",
|
|
leads: "{% trans 'Leads' %}",
|
|
numberOfOpportunities: "{% trans 'Number of Opportunities' %}"
|
|
};
|
|
|
|
// Get the canvas and message elements
|
|
const ctx_leadSources = document.getElementById('leadSourcesChart').getContext('2d');
|
|
const leadSourcesMessage = document.getElementById('leadSourcesMessage');
|
|
|
|
// Parse the JSON data from Django
|
|
const leadSourcesLabels = JSON.parse('{{ lead_sources_labels_json|safe }}');
|
|
const leadSourcesCounts = JSON.parse('{{ lead_sources_counts_json|safe }}');
|
|
|
|
// Check if there is any data to display
|
|
if (leadSourcesCounts.length > 0) {
|
|
// Show the chart and hide the message
|
|
ctx_leadSources.canvas.style.display = 'block';
|
|
if (leadSourcesMessage) {
|
|
leadSourcesMessage.style.display = 'none';
|
|
}
|
|
|
|
new Chart(ctx_leadSources, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: leadSourcesLabels,
|
|
datasets: [{
|
|
label: translatedStrings.numberOfLeads,
|
|
data: leadSourcesCounts,
|
|
backgroundColor: infoColor,
|
|
borderColor: infoColor,
|
|
borderWidth: 1
|
|
}]
|
|
},
|
|
options: {
|
|
indexAxis: 'y',
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: { display: false },
|
|
title: { display: false },
|
|
tooltip: {
|
|
backgroundColor: 'rgba(33, 37, 41, 0.9)',
|
|
titleColor: '#fff',
|
|
bodyColor: '#fff',
|
|
callbacks: {
|
|
label: function(context) {
|
|
return `${translatedStrings.leads}: ${context.parsed.x}`;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
beginAtZero: true,
|
|
title: { display: true, text: translatedStrings.numberOfLeads, color: secondaryColor },
|
|
ticks: {
|
|
color: secondaryColor,
|
|
callback: function(value) {
|
|
if (Number.isInteger(value)) {
|
|
return value;
|
|
}
|
|
}
|
|
},
|
|
grid: { color: 'rgba(0, 0, 0, 0.05)' }
|
|
},
|
|
y: {
|
|
grid: { display: false },
|
|
ticks: { color: secondaryColor }
|
|
}
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
// Hide the chart and show the message
|
|
ctx_leadSources.canvas.style.display = 'none';
|
|
if (leadSourcesMessage) {
|
|
leadSourcesMessage.style.display = 'flex';
|
|
}
|
|
}
|
|
|
|
// Lead Conversion Funnel (Horizontal Bar Chart)
|
|
const ctx_funnel = document.getElementById('leadFunnelChart').getContext('2d');
|
|
const leadFunnelMessage = document.getElementById('leadFunnelMessage');
|
|
|
|
// Parse the dynamic data from Django
|
|
const opportunityStagesLabels = JSON.parse('{{ opportunity_stage_labels_json|safe }}');
|
|
const opportunityStagesCounts = JSON.parse('{{ opportunity_stage_counts_json|safe }}');
|
|
|
|
if (opportunityStagesCounts.length > 0) {
|
|
// Show the chart and hide the message
|
|
ctx_funnel.canvas.style.display = 'block';
|
|
if (leadFunnelMessage) {
|
|
leadFunnelMessage.style.display = 'none';
|
|
}
|
|
|
|
// Get a subset of colors based on the number of data points
|
|
const backgroundColors = chartColors.slice(0, opportunityStagesCounts.length);
|
|
|
|
new Chart(ctx_funnel, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: opportunityStagesLabels,
|
|
datasets: [{
|
|
label: translatedStrings.numberOfOpportunities,
|
|
data: opportunityStagesCounts,
|
|
// Use the new backgroundColors array
|
|
backgroundColor: backgroundColors,
|
|
// Set borders to match the fill color
|
|
borderColor: backgroundColors,
|
|
borderWidth: 1
|
|
}]
|
|
},
|
|
options: {
|
|
indexAxis: 'y',
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: { display: false },
|
|
title: { display: false },
|
|
tooltip: {
|
|
backgroundColor: 'rgba(33, 37, 41, 0.9)',
|
|
titleColor: '#fff',
|
|
bodyColor: '#fff',
|
|
callbacks: {
|
|
label: function(context) {
|
|
const totalOpportunities = opportunityStagesCounts[0] || 0;
|
|
const currentOpportunities = context.parsed.x;
|
|
const percentage = totalOpportunities > 0 ? ((currentOpportunities / totalOpportunities) * 100).toFixed(1) : 0;
|
|
return `${translatedStrings.leads}: ${currentOpportunities} (${percentage}%)`;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
beginAtZero: true,
|
|
display: false
|
|
},
|
|
y: {
|
|
grid: { display: false },
|
|
ticks: { color: secondaryColor }
|
|
}
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
// Hide the chart and show the message
|
|
ctx_funnel.canvas.style.display = 'none';
|
|
if (leadFunnelMessage) {
|
|
leadFunnelMessage.style.display = 'flex';
|
|
}
|
|
}
|
|
</script>
|
|
{% endblock %}
|