haikal/templates/dashboards/general_dashboard.html
2025-08-31 19:10:32 +03:00

441 lines
19 KiB
HTML

{% extends 'base.html' %}
{% load i18n %}
{% load tenhal_tag %}
{% block title %}
{% trans "Dealership Dashboard"|capfirst %}
{% endblock title %}
{% 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">
{% if request.is_dealer %}
{% trans "Business Health Dashboard" %}
{% elif request.is_accountant and not request.is_dealer and not request.is_manager %}
{% trans "Accountant Dashboard" %}
{% elif request.is_inventory and not request.is_dealer and not request.is_manager %}
{% trans "Inventory Dashboard" %}
{% else %}
{% trans "Manager Dashboard" %}
{% endif %}
<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>
<input type="hidden" name="make_sold" value="{{ selected_make_sales }}">
</form>
</div>
<div class="row g-4 mb-5">{% include 'dashboards/partials//financial_data_cards.html' %}</div>
<div class="row g-4 mb-5">{% include 'dashboards/partials/chart.html' %}</div>
</div>
{% endblock content %}
{% block customJS %}
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
// Define a color palette that aligns with the Phoenix template
const primaryColor = '#7249b6';
const secondaryColor = '#8193a6';
const successColor = '#00d074';
const dangerColor = '#e63757';
const chartColors = [
'#7249b6', '#00d074', '#e63757', '#17a2b8', '#ffc107',
'#8193a6', '#28a745', '#6c757d', '#fd7e14', '#dc3545',
'#20c997', '#6f42c1', '#e83e8c', '#6610f2', '#007bff',
'#495057', '#0d6efd', '#6610f2', '#6f42c1', '#d63384',
'#dc3545', '#fd7e14', '#ffc107', '#198754', '#20c997',
'#0dcaf0', '#0d6efd', '#6c757d', '#adb5bd', '#6c757d',
'#5a5c69', '#36b9cc', '#1cc88a', '#f6c23e', '#e74a3b',
'#858796', '#f8f9fc', '#2c9faf', '#4e73df', '#845ef2',
'#39e6a9', '#4d4f79', '#9f1d35', '#2a5a5b', '#f77f00',
'#3282b8', '#00bcd4', '#009688', '#4caf50', '#8bc34a',
'#ffeb3b', '#ff9800', '#ff5722', '#795548', '#9e9e9e'
];
// Pass translated strings from Django to JavaScript
const translatedStrings = {
monthlyCarsSoldLabel: "{% trans 'Total Cars Sold' %}",
monthlyRevenueLabel: "{% trans 'Monthly Revenue' %}",
monthlyNetProfitLabel: "{% trans 'Monthly Net Profit' %}",
salesByMakeLabel: "{% trans 'Car Sale by Make' %}",
salesByModelPrefix: "{% trans 'Car Sale by Model' %}",
inventoryByMakeLabel: "{% trans 'Car Count by Make' %}",
inventoryByModelLabel: "{% trans 'Car Count by Model' %}",
jan: "{% trans 'Jan' %}",
feb: "{% trans 'Feb' %}",
mar: "{% trans 'Mar' %}",
apr: "{% trans 'Apr' %}",
may: "{% trans 'May' %}",
jun: "{% trans 'Jun' %}",
jul: "{% trans 'Jul' %}",
aug: "{% trans 'Aug' %}",
sep: "{% trans 'Sep' %}",
oct: "{% trans 'Oct' %}",
nov: "{% trans 'Nov' %}",
dec: "{% trans 'Dec' %}",
cars: "{% trans 'cars' %}"
};
function getChartColors(count) {
const colors = [];
for (let i = 0; i < count; i++) {
colors.push(chartColors[i % chartColors.length]);
}
return colors;
};
// Monthly Cars Sold (Bar Chart)
{% if request.is_dealer or request.is_manager or request.is_accountant %}
const ctx1 = document.getElementById('CarsSoldByMonthChart').getContext('2d');
new Chart(ctx1, {
type: 'bar',
data: {
labels: [
translatedStrings.jan, translatedStrings.feb, translatedStrings.mar, translatedStrings.apr,
translatedStrings.may, translatedStrings.jun, translatedStrings.jul, translatedStrings.aug,
translatedStrings.sep, translatedStrings.oct, translatedStrings.nov, translatedStrings.dec
],
datasets: [{
label: translatedStrings.monthlyCarsSoldLabel,
data: {{ monthly_cars_sold_json|safe }},
backgroundColor: primaryColor,
borderColor: primaryColor,
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false }
},
scales: {
y: {
beginAtZero: true,
grid: { color: 'rgba(0, 0, 0, 0.05)' },
ticks: {
color: secondaryColor,
callback: function(value) {
if (Number.isInteger(value)) {
return value;
}
}
}
},
x: {
grid: { display: false },
ticks: { color: secondaryColor }
}
}
}
});
// Monthly Revenue & Profit (Line Chart)
const ctx2 = document.getElementById('revenueProfitChart').getContext('2d');
new Chart(ctx2, {
type: 'line',
data: {
labels: [
translatedStrings.jan, translatedStrings.feb, translatedStrings.mar, translatedStrings.apr,
translatedStrings.may, translatedStrings.jun, translatedStrings.jul, translatedStrings.aug,
translatedStrings.sep, translatedStrings.oct, translatedStrings.nov, translatedStrings.dec
],
datasets: [
{
label: translatedStrings.monthlyRevenueLabel,
data: {{ monthly_revenue_json|safe }},
borderColor: primaryColor,
backgroundColor: 'rgba(114, 73, 182, 0.1)', // Using primaryColor with transparency
tension: 0.4,
fill: true,
pointBackgroundColor: primaryColor,
pointRadius: 5,
pointHoverRadius: 8
},
{
label: translatedStrings.monthlyNetProfitLabel,
data: {{ monthly_net_profit_json|safe }},
borderColor: successColor,
backgroundColor: 'rgba(0, 208, 116, 0.1)', // Using successColor with transparency
tension: 0.4,
fill: true,
pointBackgroundColor: successColor,
pointRadius: 5,
pointHoverRadius: 8
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: true,
labels: { color: '#495057', boxWidth: 20 }
},
tooltip: {
backgroundColor: 'rgba(33, 37, 41, 0.9)',
titleColor: 'white',
bodyColor: 'white',
padding: 10,
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.parsed.y !== null) {
label += new Intl.NumberFormat('en-US', { style: 'currency', currency: 'SAR' }).format(context.parsed.y);
}
return label;
}
}
}
},
scales: {
x: {
grid: { color: 'rgba(0, 0, 0, 0.05)' },
ticks: { color: secondaryColor },
border: { color: secondaryColor }
},
y: {
grid: { color: 'rgba(0, 0, 0, 0.05)' },
ticks: { color: secondaryColor },
border: { color: secondaryColor }
}
}
}
});
// Sales by Make (Pie Chart)
const ctx3 = document.getElementById('salesByBrandChart').getContext('2d');
new Chart(ctx3, {
type: 'pie',
data: {
labels: {{ sales_by_make_labels_json|safe }},
datasets: [{
label: translatedStrings.salesByMakeLabel,
data: {{ sales_by_make_counts_json|safe }},
backgroundColor: getChartColors({{ sales_by_make_counts_json|safe }}.length),
hoverOffset: 15,
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'right',
labels: { color: '#343a40', font: { size: 14 } }
},
tooltip: {
backgroundColor: 'rgba(33, 37, 41, 0.9)',
titleColor: '#fff',
bodyColor: '#fff',
callbacks: {
label: function(context) {
const label = context.label || '';
const value = context.parsed || 0;
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = ((value / total) * 100).toFixed(2);
return `${label}: ${value} ${translatedStrings.cars} (${percentage}%)`;
}
}
}
}
}
});
// -----------------------------------------------------------
// 4. Sales by Model (Bar Chart)
// -----------------------------------------------------------
const salesDataByModel = JSON.parse('{{ sales_data_by_model_json|safe }}');
const canvasElementSales = document.getElementById('salesChartByModel');
let chartInstanceSales = null;
if (salesDataByModel.length > 0) {
const labels = salesDataByModel.map(item => item.id_car_model__name);
const counts = salesDataByModel.map(item => item.count);
const backgroundColor = labels.map((_, index) => getChartColors(labels.length)[index]);
chartInstanceSales = new Chart(canvasElementSales, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: `${translatedStrings.salesByModelPrefix} {{ selected_make_sales }}`,
data: counts,
backgroundColor: backgroundColor,
borderColor: backgroundColor,
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
ticks: {
callback: function(value) {
if (Number.isInteger(value)) {
return value;
}
}
}
}
},
plugins: {
tooltip: {
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
label += Math.round(context.parsed.y);
return label;
}
}
}
}
}
});
}
{% endif %}
// -----------------------------------------------------------
// 5. Inventory by Make (Pie Chart)
// -----------------------------------------------------------
{% if request.is_dealer or request.is_manager or request.is_inventory %}
const ctxInventoryMake = document.getElementById('inventoryByMakeChart').getContext('2d');
new Chart(ctxInventoryMake, {
type: 'pie',
data: {
labels: {{ inventory_by_make_labels_json|safe }},
datasets: [{
label: translatedStrings.inventoryByMakeLabel,
data: {{ inventory_by_make_counts_json|safe }},
backgroundColor: getChartColors({{ inventory_by_make_counts_json|safe }}.length),
hoverOffset: 15,
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'right',
labels: { color: '#343a40', font: { size: 14 } }
},
tooltip: {
backgroundColor: 'rgba(33, 37, 41, 0.9)',
titleColor: '#fff',
bodyColor: '#fff',
callbacks: {
label: function(context) {
const label = context.label || '';
const value = context.parsed || 0;
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = ((value / total) * 100).toFixed(2);
return `${label}: ${value} ${translatedStrings.cars} (${percentage}%)`;
}
}
}
}
}
});
// -----------------------------------------------------------
// 6. Inventory by Model (Bar Chart)
// -----------------------------------------------------------
const inventoryDataByModel = JSON.parse('{{ inventory_data_by_model_json|safe }}');
const canvasInventoryModel = document.getElementById('inventoryByModelChart');
const messageInventoryModel = document.getElementById('inventoryByModelMessage');
if (inventoryDataByModel.length > 0) {
canvasInventoryModel.style.display = 'block';
if (messageInventoryModel) {
messageInventoryModel.style.display = 'none';
}
const labels = inventoryDataByModel.map(item => item.id_car_model__name);
const counts = inventoryDataByModel.map(item => item.count);
const backgroundColor = getChartColors(labels.length);
new Chart(canvasInventoryModel, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: translatedStrings.inventoryByModelLabel,
data: counts,
backgroundColor: backgroundColor,
borderColor: backgroundColor,
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
ticks: {
callback: function(value) {
if (Number.isInteger(value)) {
return value;
}
}
}
}
},
plugins: {
tooltip: {
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
label += Math.round(context.parsed.y);
return label;
}
}
}
}
}
});
} else {
canvasInventoryModel.style.display = 'none';
if (messageInventoryModel) {
messageInventoryModel.style.display = 'flex';
}
}
{% endif %}
</script>
{% endblock %}