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.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):
|
||||
|
||||
@ -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); }
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -60,6 +60,7 @@
|
||||
}
|
||||
</style>-->
|
||||
{% block customCSS %}
|
||||
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n static custom_filters django_ledger%}
|
||||
{% block content %}
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n static custom_filters django_ledger%}
|
||||
{% block content %}
|
||||
|
||||
|
||||
@ -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" />
|
||||
|
||||
@ -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 %}
|
||||
@ -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 = `· ${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 () {
|
||||
|
||||
@ -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">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user