Compare commits
No commits in common. "6e1e0ab1f0e41cb6909b6dde1438de5e83721e7f" and "239ea2e66e97aeb9f7f00006b2f2a413123b6d31" have entirely different histories.
6e1e0ab1f0
...
239ea2e66e
80870
database_export.json
Normal file
80870
database_export.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -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, HttpResponseForbidden
|
from django.http import JsonResponse
|
||||||
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,8 +362,6 @@ 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,25 +28,3 @@
|
|||||||
.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,7 +120,85 @@
|
|||||||
/* 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);
|
||||||
|
|
||||||
@ -1260,6 +1338,7 @@
|
|||||||
|
|
||||||
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,7 +60,6 @@
|
|||||||
}
|
}
|
||||||
</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,3 +1,4 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
{% load i18n static custom_filters django_ledger%}
|
{% load i18n static custom_filters django_ledger%}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
{% load i18n static custom_filters django_ledger%}
|
{% load i18n static custom_filters django_ledger%}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
|
|||||||
@ -5,8 +5,55 @@
|
|||||||
<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,19 +1,8 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load i18n static custom_filters django_ledger %}
|
{% load i18n static %}
|
||||||
|
|
||||||
{% 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,64 +546,39 @@
|
|||||||
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) {
|
||||||
// Create a section container
|
const parentSpec = document.createElement("div");
|
||||||
const section = document.createElement("div");
|
parentSpec.classList.add("mb-2");
|
||||||
section.classList.add("mb-4", "p-3", "border", "rounded");
|
parentSpec.innerHTML = `<strong>${parent.parent_name}</strong>`;
|
||||||
|
|
||||||
// Add section title
|
parent.specifications.forEach(function (specification) {
|
||||||
const sectionTitle = document.createElement("h4");
|
const specificationDiv = document.createElement("div");
|
||||||
sectionTitle.classList.add("mb-3", "fw-bold");
|
specificationDiv.classList.add("ms-3");
|
||||||
sectionTitle.textContent = parent.parent_name;
|
specificationDiv.innerHTML = `· ${specification.s_name}: ${specification.s_value} ${specification.s_unit}`;
|
||||||
section.appendChild(sectionTitle);
|
parentSpec.appendChild(specificationDiv);
|
||||||
|
});
|
||||||
|
|
||||||
// Create a table for the specifications
|
specificationsContent.appendChild(parentSpec);
|
||||||
const specsTable = document.createElement("div");
|
});
|
||||||
specsTable.classList.add("row");
|
} else {
|
||||||
|
specificationsContent.innerHTML = '<p>{% trans "No specifications available." %}</p>';
|
||||||
parent.specifications.forEach(function (specification) {
|
}
|
||||||
// Create a row for each specification
|
})
|
||||||
const specRow = document.createElement("div");
|
.catch((error) => {
|
||||||
specRow.classList.add("row", "mb-2");
|
specificationsContent.innerHTML = '<p>{% trans "Error loading specifications." %}</p>';
|
||||||
|
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">{{ _("Available") }}</span>
|
<span class="badge badge-phoenix fs-11 badge-phoenix-success">{{car.status}}</span>
|
||||||
{% elif car.status == "reserved" %}
|
{% elif car.status == "reserved" %}
|
||||||
<span class="badge badge-phoenix fs-11 badge-phoenix-danger">{{ _("Reserved") }}</span>
|
<span class="badge badge-phoenix fs-11 badge-phoenix-danger">{{car.status}}</span>
|
||||||
{% elif car.status == "sold" %}
|
{% elif car.status == "sold" %}
|
||||||
<span class="badge badge-phoenix fs-11 badge-phoenix-info">{{ _("Sold") }}</span>
|
<span class="badge badge-phoenix fs-11 badge-phoenix-info">{{car.status}}</span>
|
||||||
{% elif car.status == "transfer" %}
|
{% elif car.status == "transfer" %}
|
||||||
<span class="badge badge-phoenix fs-11 badge-phoenix-warning">{{ _("Transfer") }}</span>
|
<span class="badge badge-phoenix fs-11 badge-phoenix-warning">{{car.status}}</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