This commit is contained in:
Marwan Alwali 2025-05-03 15:51:01 +03:00
parent 239ea2e66e
commit 3267630578
10 changed files with 107 additions and 174 deletions

View File

@ -22,7 +22,7 @@ from django.conf import settings
from django.db import transaction
from django.db.models import Func
from django.contrib import messages
from django.http import JsonResponse
from django.http import JsonResponse, HttpResponseForbidden
from django.forms import HiddenInput, ValidationError
from django.shortcuts import HttpResponse
from django.db.models import Sum, F, Count
@ -362,6 +362,8 @@ class ManagerDashboard(LoginRequiredMixin, TemplateView):
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return redirect("welcome")
if not getattr(request.user, 'dealer', False):
return HttpResponseForbidden("You are not authorized to view this dashboard.")
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):

View File

@ -27,4 +27,26 @@
.rtl .fa-chevron-right {
transform: scaleX(-1);
}
}
.spinner-container {
display: flex;
align-items: center;
gap: 10px;
padding: 10px;
color: #555;
font-size: 14px;
}
.spinner {
width: 18px;
height: 18px;
border: 3px solid #ccc;
border-top-color: #333;
border-radius: 50%;
animation: spin 0.6s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}

View File

@ -120,85 +120,7 @@
/* Echarts Total Sales */
/* -------------------------------------------------------------------------- */
const contactsBySourceChartInit = () => {
const { getColor, getData, toggleColor } = window.phoenix.utils;
const chartElContainer = document.querySelector(
'.echart-contact-by-source-container'
);
const chartEl = chartElContainer.querySelector('.echart-contact-by-source');
const chartLabel = chartElContainer.querySelector('[data-label]');
if (chartEl) {
const userOptions = getData(chartEl, 'echarts');
const chart = window.echarts.init(chartEl);
const data = [
{ value: 80, name: 'Organic Search' },
{ value: 65, name: 'Paid Search' },
{ value: 40, name: 'Direct Traffic' },
{ value: 220, name: 'Social Media' },
{ value: 120, name: 'Referrals' },
{ value: 35, name: 'Others Campaigns' }
];
const totalSource = data.reduce((acc, val) => val.value + acc, 0);
if (chartLabel) {
chartLabel.innerHTML = totalSource;
}
const getDefaultOptions = () => ({
color: [
getColor('primary'),
getColor('success'),
getColor('info'),
getColor('info-light'),
toggleColor(getColor('danger-lighter'), getColor('danger-darker')),
toggleColor(getColor('warning-light'), getColor('warning-dark'))
],
tooltip: {
trigger: 'item',
borderWidth: 0,
position: (...params) => handleTooltipPosition(params),
extraCssText: 'z-index: 1000'
},
responsive: true,
maintainAspectRatio: false,
series: [
{
name: 'Contacts by Source',
type: 'pie',
radius: ['55%', '90%'],
startAngle: 90,
avoidLabelOverlap: false,
itemStyle: {
borderColor: getColor('body-bg'),
borderWidth: 3
},
label: {
show: false
},
emphasis: {
label: {
show: false
}
},
labelLine: {
show: false
},
data
}
],
grid: {
bottom: 0,
top: 0,
left: 0,
right: 0,
containLabel: false
}
});
echartSetOption(chart, userOptions, getDefaultOptions);
}
};
// dayjs.extend(advancedFormat);
@ -1338,7 +1260,6 @@
const { docReady } = window.phoenix.utils;
docReady(contactsBySourceChartInit);
docReady(contactsCreatedChartInit);
docReady(newUsersChartsInit);
docReady(newLeadsChartsInit);

View File

@ -60,6 +60,7 @@
}
</style>-->
{% block customCSS %}
{% endblock %}
</head>
<body hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>

View File

@ -1,4 +1,3 @@
{% extends 'base.html' %}
{% load i18n static custom_filters django_ledger%}
{% block content %}

View File

@ -1,4 +1,3 @@
{% extends 'base.html' %}
{% load i18n static custom_filters django_ledger%}
{% block content %}

View File

@ -5,55 +5,8 @@
<div class="collapse navbar-collapse" id="navbarVerticalCollapse">
<div class="navbar-vertical-content">
<ul class="navbar-nav flex-column" id="navbarVerticalNav">
<li class="nav-item">
<div class="nav-item-wrapper">
<a class="nav-link dropdown-indicator label-1" href="#nv-dashboards" role="button" data-bs-toggle="collapse" aria-expanded="false" aria-controls="nv-dashboards">
<div class="d-flex align-items-center">
<div class="dropdown-indicator-icon-wrapper"><span class="fas fa-caret-right dropdown-indicator-icon"></span></div>
<span class="nav-link-icon">
<span data-feather="pie-chart"></span>
</span>
<span class="nav-link-text">{{ _("Dashboards") }}</span>
</div>
</a>
<div class="parent-wrapper label-1">
<ul class="nav collapse parent" data-bs-parent="#navbarVerticalCollapse" id="nv-dashboards">
<li class="collapsed-nav-item-title d-none">{{ _("Dashboards") }}</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'manager_dashboard' %}">
<div class="d-flex align-items-center"><span class="nav-link-text">{{ _("Manager") }}</span></div>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<div class="d-flex align-items-center"><span class="nav-link-text">{{ _("Inventory") }}</span></div>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'sales_dashboard' %}">
<div class="d-flex align-items-center"><span class="nav-link-text">{{ _("Sales") }}</span></div>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<div class="d-flex align-items-center"><span class="nav-link-text">{{ _("CRM") }}</span></div>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<div class="d-flex align-items-center"><span class="nav-link-text">{{ _("Financials") }}</span></div>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">
<div class="d-flex align-items-center"><span class="nav-link-text">{{ _("Reports") }}</span></div>
</a>
</li>
</ul>
</div>
</div>
</li>
<hr class="my-0" />
<li class="nav-item">
<p class="navbar-vertical-label">Apps</p>
<hr class="navbar-vertical-line" />

View File

@ -1,8 +1,19 @@
{% extends 'base.html' %}
{% load i18n static %}
{% load i18n static custom_filters django_ledger %}
{% block content %}
{% if request.user.is_authenticated %}
<div
id="dashboard-content"
hx-get="{% if request.user.dealer %}{% url 'manager_dashboard' %}{% else %}{% url 'sales_dashboard' %}{% endif %}"
hx-trigger="load"
hx-target="#dashboard-content"
hx-swap="innerHTML"
>
<div class="spinner-container">
<div class="spinner"></div>
<p>Loading dashboard...</p>
</div>
</div>
{% endif %}
{% endblock %}

View File

@ -546,39 +546,64 @@
const specificationsContent = document.getElementById("specificationsContent");
showSpecificationButton.addEventListener("click", function () {
specificationsContent.innerHTML = "";
fetch(`${ajaxUrl}?action=get_specifications&trim_id={{ car.id_car_trim.id_car_trim }}`, {
headers: {
"X-Requested-With": "XMLHttpRequest",
"X-CSRFToken": csrftoken,
},
})
.then((response) => response.json())
.then((data) => {
if (data.length > 0) {
data.forEach(function (parent) {
const parentSpec = document.createElement("div");
parentSpec.classList.add("mb-2");
parentSpec.innerHTML = `<strong>${parent.parent_name}</strong>`;
specificationsContent.innerHTML = "";
fetch(`${ajaxUrl}?action=get_specifications&trim_id={{ car.id_car_trim.id_car_trim }}`, {
headers: {
"X-Requested-With": "XMLHttpRequest",
"X-CSRFToken": csrftoken,
},
})
.then((response) => response.json())
.then((data) => {
if (data.length > 0) {
data.forEach(function (parent) {
// Create a section container
const section = document.createElement("div");
section.classList.add("mb-4", "p-3", "border", "rounded");
parent.specifications.forEach(function (specification) {
const specificationDiv = document.createElement("div");
specificationDiv.classList.add("ms-3");
specificationDiv.innerHTML = `&#183; ${specification.s_name}: ${specification.s_value} ${specification.s_unit}`;
parentSpec.appendChild(specificationDiv);
});
// Add section title
const sectionTitle = document.createElement("h4");
sectionTitle.classList.add("mb-3", "fw-bold");
sectionTitle.textContent = parent.parent_name;
section.appendChild(sectionTitle);
specificationsContent.appendChild(parentSpec);
});
} else {
specificationsContent.innerHTML = '<p>{% trans "No specifications available." %}</p>';
}
})
.catch((error) => {
specificationsContent.innerHTML = '<p>{% trans "Error loading specifications." %}</p>';
console.error("Error fetching specifications:", error);
// Create a table for the specifications
const specsTable = document.createElement("div");
specsTable.classList.add("row");
parent.specifications.forEach(function (specification) {
// Create a row for each specification
const specRow = document.createElement("div");
specRow.classList.add("row", "mb-2");
// Left Column: Spec name
const specName = document.createElement("div");
specName.classList.add("col-6", "text-muted");
specName.textContent = specification.s_name;
// Right Column: Spec value + unit
const specValue = document.createElement("div");
specValue.classList.add("col-6", "text-end");
specValue.textContent = `${specification.s_value} ${specification.s_unit || ""}`;
specRow.appendChild(specName);
specRow.appendChild(specValue);
specsTable.appendChild(specRow);
});
section.appendChild(specsTable);
specificationsContent.appendChild(section);
});
} else {
specificationsContent.innerHTML = '<p>{% trans "No specifications available." %}</p>';
}
})
.catch((error) => {
specificationsContent.innerHTML = '<p>{% trans "Error loading specifications." %}</p>';
console.error("Error fetching specifications:", error);
});
});
document.querySelectorAll(".reserve-btn").forEach((button) => {
button.addEventListener("click", async function () {

View File

@ -34,11 +34,11 @@
hx-on::before-request="on_before_request()"
hx-on::after-request="on_after_request()"
>
<li class="nav-item"><a class="nav-link px-2 py-1 active" aria-current="page" href="{% url 'car_list' %}"><span>All</span><span class="text-body-tertiary fw-semibold">({{stats.all}})</span></a></li>
<li class="nav-item"><a class="nav-link px-2 py-1" href="{% url 'car_list' %}?status=available"><span>Available</span><span class="text-body-tertiary fw-semibold">({{stats.available}})</span></a></li>
<li class="nav-item"><a class="nav-link px-2 py-1" href="{% url 'car_list' %}?status=reserved"><span>Reserved</span><span class="text-body-tertiary fw-semibold">({{stats.reserved}})</span></a></li>
<li class="nav-item"><a class="nav-link px-2 py-1" href="{% url 'car_list' %}?status=transfer"><span>Transfer</span><span class="text-body-tertiary fw-semibold">({{stats.transfer}})</span></a></li>
<li class="nav-item"><a class="nav-link px-2 py-1" href="{% url 'car_list' %}?status=sold"><span>Sold</span><span class="text-body-tertiary fw-semibold">({{stats.sold}})</span></a></li>
<li class="nav-item"><a class="nav-link px-2 py-1 active" aria-current="page" href="{% url 'car_list' %}"><span>{{ _("All") }}</span><span class="text-body-tertiary fw-semibold">({{stats.all}})</span></a></li>
<li class="nav-item"><a class="nav-link px-2 py-1" href="{% url 'car_list' %}?status=available"><span>{{ _("Available") }}</span><span class="text-body-tertiary fw-semibold">({{stats.available}})</span></a></li>
<li class="nav-item"><a class="nav-link px-2 py-1" href="{% url 'car_list' %}?status=reserved"><span>{{ _("Reserved") }}</span><span class="text-body-tertiary fw-semibold">({{stats.reserved}})</span></a></li>
<li class="nav-item"><a class="nav-link px-2 py-1" href="{% url 'car_list' %}?status=transfer"><span>{{ _("Transfer") }}</span><span class="text-body-tertiary fw-semibold">({{stats.transfer}})</span></a></li>
<li class="nav-item"><a class="nav-link px-2 py-1" href="{% url 'car_list' %}?status=sold"><span>{{ _("Sold") }}</span><span class="text-body-tertiary fw-semibold">({{stats.sold}})</span></a></li>
<li class="nav-item"><button hx-on:click="toggle_filter()" class="btn btn-sm btn-primary px-2 py-1"><span><span class="fa fa-filter me-1"></span>{{ _("Filter") }}</span><span class="fas fa-caret-down fs-9 ms-1 filter-icon"></span></button></li>
</ul>
</div>
@ -151,13 +151,13 @@
<td class="align-middle white-space-nowrap statuses">
{% if car.status == "available" %}
<span class="badge badge-phoenix fs-11 badge-phoenix-success">{{car.status}}</span>
<span class="badge badge-phoenix fs-11 badge-phoenix-success">{{ _("Available") }}</span>
{% elif car.status == "reserved" %}
<span class="badge badge-phoenix fs-11 badge-phoenix-danger">{{car.status}}</span>
<span class="badge badge-phoenix fs-11 badge-phoenix-danger">{{ _("Reserved") }}</span>
{% elif car.status == "sold" %}
<span class="badge badge-phoenix fs-11 badge-phoenix-info">{{car.status}}</span>
<span class="badge badge-phoenix fs-11 badge-phoenix-info">{{ _("Sold") }}</span>
{% elif car.status == "transfer" %}
<span class="badge badge-phoenix fs-11 badge-phoenix-warning">{{car.status}}</span>
<span class="badge badge-phoenix fs-11 badge-phoenix-warning">{{ _("Transfer") }}</span>
{% endif %}
</td>
<td class="align-middle text-end white-space-nowrap pe-0 action">