update
This commit is contained in:
parent
239ea2e66e
commit
3267630578
@ -22,7 +22,7 @@ from django.conf import settings
|
|||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Func
|
from django.db.models import Func
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse, HttpResponseForbidden
|
||||||
from django.forms import HiddenInput, ValidationError
|
from django.forms import HiddenInput, ValidationError
|
||||||
from django.shortcuts import HttpResponse
|
from django.shortcuts import HttpResponse
|
||||||
from django.db.models import Sum, F, Count
|
from django.db.models import Sum, F, Count
|
||||||
@ -362,6 +362,8 @@ class ManagerDashboard(LoginRequiredMixin, TemplateView):
|
|||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
if not request.user.is_authenticated:
|
if not request.user.is_authenticated:
|
||||||
return redirect("welcome")
|
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)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
|||||||
@ -28,3 +28,25 @@
|
|||||||
.rtl .fa-chevron-right {
|
.rtl .fa-chevron-right {
|
||||||
transform: scaleX(-1);
|
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); }
|
||||||
|
}
|
||||||
|
|||||||
@ -120,85 +120,7 @@
|
|||||||
/* Echarts Total Sales */
|
/* 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);
|
// dayjs.extend(advancedFormat);
|
||||||
|
|
||||||
@ -1338,7 +1260,6 @@
|
|||||||
|
|
||||||
const { docReady } = window.phoenix.utils;
|
const { docReady } = window.phoenix.utils;
|
||||||
|
|
||||||
docReady(contactsBySourceChartInit);
|
|
||||||
docReady(contactsCreatedChartInit);
|
docReady(contactsCreatedChartInit);
|
||||||
docReady(newUsersChartsInit);
|
docReady(newUsersChartsInit);
|
||||||
docReady(newLeadsChartsInit);
|
docReady(newLeadsChartsInit);
|
||||||
|
|||||||
@ -60,6 +60,7 @@
|
|||||||
}
|
}
|
||||||
</style>-->
|
</style>-->
|
||||||
{% block customCSS %}
|
{% block customCSS %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
|
<body hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load i18n static custom_filters django_ledger%}
|
{% load i18n static custom_filters django_ledger%}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load i18n static custom_filters django_ledger%}
|
{% load i18n static custom_filters django_ledger%}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
|||||||
@ -5,55 +5,8 @@
|
|||||||
<div class="collapse navbar-collapse" id="navbarVerticalCollapse">
|
<div class="collapse navbar-collapse" id="navbarVerticalCollapse">
|
||||||
<div class="navbar-vertical-content">
|
<div class="navbar-vertical-content">
|
||||||
<ul class="navbar-nav flex-column" id="navbarVerticalNav">
|
<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">
|
<li class="nav-item">
|
||||||
<p class="navbar-vertical-label">Apps</p>
|
<p class="navbar-vertical-label">Apps</p>
|
||||||
<hr class="navbar-vertical-line" />
|
<hr class="navbar-vertical-line" />
|
||||||
|
|||||||
@ -1,8 +1,19 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load i18n static %}
|
{% load i18n static custom_filters django_ledger %}
|
||||||
|
|
||||||
{% block content %}
|
{% 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 %}
|
{% endblock %}
|
||||||
@ -546,39 +546,64 @@
|
|||||||
const specificationsContent = document.getElementById("specificationsContent");
|
const specificationsContent = document.getElementById("specificationsContent");
|
||||||
|
|
||||||
showSpecificationButton.addEventListener("click", function () {
|
showSpecificationButton.addEventListener("click", function () {
|
||||||
specificationsContent.innerHTML = "";
|
specificationsContent.innerHTML = "";
|
||||||
fetch(`${ajaxUrl}?action=get_specifications&trim_id={{ car.id_car_trim.id_car_trim }}`, {
|
fetch(`${ajaxUrl}?action=get_specifications&trim_id={{ car.id_car_trim.id_car_trim }}`, {
|
||||||
headers: {
|
headers: {
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
"X-CSRFToken": csrftoken,
|
"X-CSRFToken": csrftoken,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.length > 0) {
|
if (data.length > 0) {
|
||||||
data.forEach(function (parent) {
|
data.forEach(function (parent) {
|
||||||
const parentSpec = document.createElement("div");
|
// Create a section container
|
||||||
parentSpec.classList.add("mb-2");
|
const section = document.createElement("div");
|
||||||
parentSpec.innerHTML = `<strong>${parent.parent_name}</strong>`;
|
section.classList.add("mb-4", "p-3", "border", "rounded");
|
||||||
|
|
||||||
parent.specifications.forEach(function (specification) {
|
// Add section title
|
||||||
const specificationDiv = document.createElement("div");
|
const sectionTitle = document.createElement("h4");
|
||||||
specificationDiv.classList.add("ms-3");
|
sectionTitle.classList.add("mb-3", "fw-bold");
|
||||||
specificationDiv.innerHTML = `· ${specification.s_name}: ${specification.s_value} ${specification.s_unit}`;
|
sectionTitle.textContent = parent.parent_name;
|
||||||
parentSpec.appendChild(specificationDiv);
|
section.appendChild(sectionTitle);
|
||||||
});
|
|
||||||
|
|
||||||
specificationsContent.appendChild(parentSpec);
|
// Create a table for the specifications
|
||||||
});
|
const specsTable = document.createElement("div");
|
||||||
} else {
|
specsTable.classList.add("row");
|
||||||
specificationsContent.innerHTML = '<p>{% trans "No specifications available." %}</p>';
|
|
||||||
}
|
parent.specifications.forEach(function (specification) {
|
||||||
})
|
// Create a row for each specification
|
||||||
.catch((error) => {
|
const specRow = document.createElement("div");
|
||||||
specificationsContent.innerHTML = '<p>{% trans "Error loading specifications." %}</p>';
|
specRow.classList.add("row", "mb-2");
|
||||||
console.error("Error fetching specifications:", error);
|
|
||||||
|
// 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) => {
|
document.querySelectorAll(".reserve-btn").forEach((button) => {
|
||||||
button.addEventListener("click", async function () {
|
button.addEventListener("click", async function () {
|
||||||
|
|||||||
@ -34,11 +34,11 @@
|
|||||||
hx-on::before-request="on_before_request()"
|
hx-on::before-request="on_before_request()"
|
||||||
hx-on::after-request="on_after_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 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=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=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=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" 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>
|
<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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@ -151,13 +151,13 @@
|
|||||||
|
|
||||||
<td class="align-middle white-space-nowrap statuses">
|
<td class="align-middle white-space-nowrap statuses">
|
||||||
{% if car.status == "available" %}
|
{% 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" %}
|
{% 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" %}
|
{% 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" %}
|
{% 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 %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="align-middle text-end white-space-nowrap pe-0 action">
|
<td class="align-middle text-end white-space-nowrap pe-0 action">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user