update on the htmx lead
This commit is contained in:
parent
f8527795ae
commit
14680585b9
@ -80,7 +80,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main class="main" id="top">
|
<main class="main" id="top">
|
||||||
<div class="px-3">
|
<div id="main_content" class="px-3">
|
||||||
<div class="row min-vh-100 flex-center p-5">
|
<div class="row min-vh-100 flex-center p-5">
|
||||||
<div class="col-12 col-xl-10 col-xxl-8">
|
<div class="col-12 col-xl-10 col-xxl-8">
|
||||||
<div class="row justify-content-center align-items-center g-5">
|
<div class="row justify-content-center align-items-center g-5">
|
||||||
|
|||||||
@ -66,7 +66,7 @@
|
|||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
id="user-style-default">
|
id="user-style-default">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<script src="{% static 'js/main.js' %}"></script>
|
<script src="{% static 'js/main.js' %}"></script>
|
||||||
<script src="{% static 'js/jquery.min.js' %}"></script>
|
<script src="{% static 'js/jquery.min.js' %}"></script>
|
||||||
{% comment %} <script src="{% static 'js/echarts.js' %}"></script> {% endcomment %}
|
{% comment %} <script src="{% static 'js/echarts.js' %}"></script> {% endcomment %}
|
||||||
@ -83,9 +83,9 @@
|
|||||||
{% block period_navigation %}
|
{% block period_navigation %}
|
||||||
{% endblock period_navigation %}
|
{% endblock period_navigation %}
|
||||||
<div id="main_content" class="fade-me-in" hx-boost="true" hx-target="#main_content" hx-select="#main_content" hx-swap="outerHTML transition:false" hx-select-oob="#toast-container" hx-history-elt>
|
<div id="main_content" class="fade-me-in" hx-boost="true" hx-target="#main_content" hx-select="#main_content" hx-swap="outerHTML transition:false" hx-select-oob="#toast-container" hx-history-elt>
|
||||||
{% comment %} <div id="spinner" class="htmx-indicator spinner-bg">
|
<div id="spinner" class="htmx-indicator spinner-bg">
|
||||||
<img src="{% static 'spinner.svg' %}" width="100" height="100" alt="">
|
<img src="{% static 'spinner.svg' %}" width="100" height="100" alt="">
|
||||||
</div> {% endcomment %}
|
</div>
|
||||||
{% block customCSS %}{% endblock %}
|
{% block customCSS %}{% endblock %}
|
||||||
{% block content %}{% endblock content %}
|
{% block content %}{% endblock content %}
|
||||||
{% block customJS %}{% endblock %}
|
{% block customJS %}{% endblock %}
|
||||||
@ -154,7 +154,38 @@
|
|||||||
document.getElementById('global-indicator')
|
document.getElementById('global-indicator')
|
||||||
];
|
];
|
||||||
});*/
|
});*/
|
||||||
|
let Toast = Swal.mixin({
|
||||||
|
toast: true,
|
||||||
|
position: "top-end",
|
||||||
|
showConfirmButton: false,
|
||||||
|
timer: 3000,
|
||||||
|
timerProgressBar: true,
|
||||||
|
didOpen: (toast) => {
|
||||||
|
toast.onmouseenter = Swal.stopTimer;
|
||||||
|
toast.onmouseleave = Swal.resumeTimer;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
function notify(tag, msg) {
|
||||||
|
Toast.fire({
|
||||||
|
icon: tag,
|
||||||
|
titleText: msg
|
||||||
|
});
|
||||||
|
}
|
||||||
|
document.addEventListener('htmx:afterRequest', function(evt) {
|
||||||
|
if(evt.detail.xhr.status == 403){
|
||||||
|
/* Notify the user of a 404 Not Found response */
|
||||||
|
notify("error", "You do not have permission to view this page");
|
||||||
|
}
|
||||||
|
if(evt.detail.xhr.status == 404){
|
||||||
|
/* Notify the user of a 404 Not Found response */
|
||||||
|
return alert("Error: Could Not Find Resource");
|
||||||
|
}
|
||||||
|
if (evt.detail.successful != true) {
|
||||||
|
console.log(evt)
|
||||||
|
/* Notify of an unexpected error, & print error to console */
|
||||||
|
notify("error", "Unexpected Error");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{% comment %} {% block customJS %}{% endblock %} {% endcomment %}
|
{% comment %} {% block customJS %}{% endblock %} {% endcomment %}
|
||||||
|
|||||||
@ -494,7 +494,8 @@
|
|||||||
data-url="{% url 'update_note' request.dealer.slug note.pk %}"
|
data-url="{% url 'update_note' request.dealer.slug note.pk %}"
|
||||||
data-bs-toggle="modal"
|
data-bs-toggle="modal"
|
||||||
data-bs-target="#noteModal"
|
data-bs-target="#noteModal"
|
||||||
data-note-title="{{ _("Update") }}<i class='fas fa-pen-square text-primary ms-2'></i>">
|
data-note-title="{{ _('Update') }}">
|
||||||
|
<i class='fas fa-pen-square text-primary ms-2'></i>
|
||||||
{{ _("Update") }}
|
{{ _("Update") }}
|
||||||
</a>
|
</a>
|
||||||
<button class="btn btn-phoenix-danger btn-sm delete-btn"
|
<button class="btn btn-phoenix-danger btn-sm delete-btn"
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
<li class="collapsed-nav-item-title d-none">{% trans "Inventory"|capfirst %}</li>
|
<li class="collapsed-nav-item-title d-none">{% trans "Inventory"|capfirst %}</li>
|
||||||
{% if perms.inventory.add_car %}
|
{% if perms.inventory.add_car %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a hx-boost="false" id="btn-add-car" class="nav-link btn-add-car" href="{% url 'car_add' request.dealer.slug %}">
|
<a hx-boost="true" id="btn-add-car" class="nav-link btn-add-car" href="{% url 'car_add' request.dealer.slug %}">
|
||||||
<div class="d-flex align-items-center">
|
<div class="d-flex align-items-center">
|
||||||
<span class="nav-link-icon"><span class="fas fa-plus-circle"></span></span><span class="nav-link-text">{% trans "add car"|capfirst %}</span>
|
<span class="nav-link-icon"><span class="fas fa-plus-circle"></span></span><span class="nav-link-text">{% trans "add car"|capfirst %}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -339,336 +339,435 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!---->
|
<!---->
|
||||||
<script>
|
{% endblock content %}
|
||||||
function getCookie(name) {
|
{% block customJS %}
|
||||||
let cookieValue = null;
|
<script>
|
||||||
if (document.cookie && document.cookie !== "") {
|
// Global variables
|
||||||
const cookies = document.cookie.split(";");
|
let codeReader;
|
||||||
for (let cookie of cookies) {
|
let currentStream = null;
|
||||||
cookie = cookie.trim();
|
const csrfToken = getCookie("csrftoken");
|
||||||
if (cookie.substring(0, name.length + 1) === name + "=") {
|
const ajaxUrl = "{% url 'ajax_handler' request.dealer.slug %}";
|
||||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
|
||||||
break;
|
// Initialize when page loads and after HTMX swaps
|
||||||
}
|
document.addEventListener('DOMContentLoaded', initPage);
|
||||||
}
|
document.addEventListener('htmx:afterSwap', initPage);
|
||||||
}
|
|
||||||
return cookieValue;
|
function initPage() {
|
||||||
|
// Get DOM elements
|
||||||
|
const elements = {
|
||||||
|
vinInput: document.getElementById("{{ form.vin.id_for_label }}"),
|
||||||
|
decodeVinBtn: document.getElementById("decodeVinBtn"),
|
||||||
|
makeSelect: document.getElementById("{{ form.id_car_make.id_for_label }}"),
|
||||||
|
modelSelect: document.getElementById("{{ form.id_car_model.id_for_label }}"),
|
||||||
|
yearSelect: document.getElementById("{{ form.year.id_for_label }}"),
|
||||||
|
serieSelect: document.getElementById("{{ form.id_car_serie.id_for_label }}"),
|
||||||
|
trimSelect: document.getElementById("{{ form.id_car_trim.id_for_label }}"),
|
||||||
|
equipmentSelect: document.getElementById("equipment_id"),
|
||||||
|
showSpecificationButton: document.getElementById("specification-btn"),
|
||||||
|
showEquipmentButton: document.getElementById("options-btn"),
|
||||||
|
specificationsContent: document.getElementById("specificationsContent"),
|
||||||
|
optionsContent: document.getElementById("optionsContent"),
|
||||||
|
generationContainer: document.getElementById("generation-div"),
|
||||||
|
closeButton: document.querySelector(".btn-close"),
|
||||||
|
scanVinBtn: document.getElementById("scan-vin-btn"),
|
||||||
|
videoElement: document.getElementById("video"),
|
||||||
|
resultDisplay: document.getElementById("result"),
|
||||||
|
fallbackButton: document.getElementById("ocr-fallback-btn")
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize scanner if available
|
||||||
|
if (typeof ZXing !== 'undefined' && !codeReader) {
|
||||||
|
codeReader = new ZXing.BrowserMultiFormatReader();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add event listeners
|
||||||
|
setupEventListeners(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupEventListeners(elements) {
|
||||||
|
// Remove existing listeners first to prevent duplicates
|
||||||
|
removeEventListeners(elements);
|
||||||
|
|
||||||
|
// Add new listeners
|
||||||
|
if (elements.decodeVinBtn) {
|
||||||
|
elements.decodeVinBtn.addEventListener("click", decodeVin);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elements.scanVinBtn) {
|
||||||
|
elements.scanVinBtn.addEventListener("click", () => {
|
||||||
|
elements.resultDisplay.textContent = "";
|
||||||
|
startScanner(elements.videoElement);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elements.fallbackButton) {
|
||||||
|
elements.fallbackButton.addEventListener("click", () => {
|
||||||
|
captureAndOCR(elements.videoElement, elements.vinInput);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elements.serieSelect) {
|
||||||
|
elements.serieSelect.addEventListener("change", () => {
|
||||||
|
const serie_id = elements.serieSelect.value;
|
||||||
|
const model_id = elements.modelSelect.value;
|
||||||
|
if (serie_id && model_id) loadTrims(serie_id, model_id, elements);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elements.trimSelect) {
|
||||||
|
elements.trimSelect.addEventListener("change", () => {
|
||||||
|
const trimId = elements.trimSelect.value;
|
||||||
|
elements.showSpecificationButton.disabled = !trimId;
|
||||||
|
elements.showEquipmentButton.disabled = !trimId;
|
||||||
|
if (trimId) loadSpecifications(trimId, elements);
|
||||||
|
loadEquipment(trimId, elements);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elements.equipmentSelect) {
|
||||||
|
elements.equipmentSelect.addEventListener("change", () => {
|
||||||
|
const equipmentId = elements.equipmentSelect.value;
|
||||||
|
if (equipmentId) loadOptions(equipmentId, elements);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elements.closeButton) {
|
||||||
|
elements.closeButton.addEventListener("click", () => closeModal(elements.scanVinBtn));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elements.makeSelect) {
|
||||||
|
elements.makeSelect.addEventListener("change", (e) => {
|
||||||
|
loadModels(e.target.value, elements.modelSelect.value, elements);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elements.modelSelect) {
|
||||||
|
elements.modelSelect.addEventListener("change", (e) => {
|
||||||
|
loadSeries(e.target.value, elements.yearSelect.value, elements);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeEventListeners(elements) {
|
||||||
|
// Remove all event listeners to prevent duplicates
|
||||||
|
const events = [
|
||||||
|
{ element: elements.decodeVinBtn, event: "click", func: decodeVin },
|
||||||
|
{ element: elements.scanVinBtn, event: "click" },
|
||||||
|
{ element: elements.fallbackButton, event: "click" },
|
||||||
|
{ element: elements.serieSelect, event: "change" },
|
||||||
|
{ element: elements.trimSelect, event: "change" },
|
||||||
|
{ element: elements.equipmentSelect, event: "change" },
|
||||||
|
{ element: elements.closeButton, event: "click" },
|
||||||
|
{ element: elements.makeSelect, event: "change" },
|
||||||
|
{ element: elements.modelSelect, event: "change" }
|
||||||
|
];
|
||||||
|
|
||||||
|
events.forEach(item => {
|
||||||
|
if (item.element) {
|
||||||
|
item.element.removeEventListener(item.event, item.func || null);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
// Cookie helper function
|
||||||
const csrfToken = getCookie("csrftoken");
|
function getCookie(name) {
|
||||||
|
let cookieValue = null;
|
||||||
const vinInput = document.getElementById("{{ form.vin.id_for_label }}");
|
if (document.cookie && document.cookie !== "") {
|
||||||
const decodeVinBtn = document.getElementById("decodeVinBtn");
|
const cookies = document.cookie.split(";");
|
||||||
const makeSelect = document.getElementById("{{ form.id_car_make.id_for_label }}");
|
for (let cookie of cookies) {
|
||||||
const modelSelect = document.getElementById("{{ form.id_car_model.id_for_label }}");
|
cookie = cookie.trim();
|
||||||
const yearSelect = document.getElementById("{{ form.year.id_for_label }}");
|
if (cookie.substring(0, name.length + 1) === name + "=") {
|
||||||
const serieSelect = document.getElementById("{{ form.id_car_serie.id_for_label }}");
|
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||||
const trimSelect = document.getElementById("{{ form.id_car_trim.id_for_label }}");
|
break;
|
||||||
const equipmentSelect = document.getElementById("equipment_id")
|
|
||||||
const showSpecificationButton = document.getElementById("specification-btn");
|
|
||||||
const showEquipmentButton = document.getElementById("options-btn")
|
|
||||||
const specificationsContent = document.getElementById("specificationsContent");
|
|
||||||
const optionsContent = document.getElementById("optionsContent")
|
|
||||||
const generationContainer = document.getElementById("generation-div")
|
|
||||||
|
|
||||||
const ajaxUrl = "{% url 'ajax_handler' request.dealer.slug %}";
|
|
||||||
|
|
||||||
const closeButton = document.querySelector(".btn-close");
|
|
||||||
const scanVinBtn = document.getElementById("scan-vin-btn");
|
|
||||||
const videoElement = document.getElementById("video");
|
|
||||||
const resultDisplay = document.getElementById("result");
|
|
||||||
const fallbackButton = document.getElementById("ocr-fallback-btn");
|
|
||||||
let codeReader;
|
|
||||||
codeReader = new ZXing.BrowserMultiFormatReader();
|
|
||||||
let currentStream = null;
|
|
||||||
|
|
||||||
function closeModal() {
|
|
||||||
stopScanner();
|
|
||||||
try {
|
|
||||||
const scannerModal = document.getElementById("scannerModal");
|
|
||||||
if (scannerModal) {
|
|
||||||
document.activeElement.blur();
|
|
||||||
scannerModal.setAttribute("inert", "true");
|
|
||||||
const modalInstance = bootstrap.Modal.getInstance(scannerModal);
|
|
||||||
if (modalInstance) modalInstance.hide();
|
|
||||||
if (scanVinBtn) scanVinBtn.focus();
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Error closing scanner modal:", err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cookieValue;
|
||||||
|
}
|
||||||
|
|
||||||
async function decodeVin() {
|
// VIN Decoding functions
|
||||||
const vinNumber = vinInput.value.trim();
|
async function decodeVin() {
|
||||||
if (vinNumber.length !== 17) {
|
const vinInput = document.getElementById("{{ form.vin.id_for_label }}");
|
||||||
Swal.fire("error", "{% trans 'Please enter a valid VIN.' %}");
|
const vinNumber = vinInput.value.trim();
|
||||||
/*alert("{% trans 'Please enter a valid VIN.' %}");*/
|
|
||||||
return;
|
if (vinNumber.length !== 17) {
|
||||||
}
|
Swal.fire("error", "{% trans 'Please enter a valid VIN.' %}");
|
||||||
showLoading();
|
return;
|
||||||
try {
|
}
|
||||||
const response = await fetch(`${ajaxUrl}?action=decode_vin&vin_no=${vinNumber}`, {
|
|
||||||
headers: {
|
showLoading();
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
try {
|
||||||
"X-CSRFToken": csrfToken,
|
const response = await fetch(`${ajaxUrl}?action=decode_vin&vin_no=${vinNumber}`, {
|
||||||
},
|
headers: {
|
||||||
});
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
const data = await response.json();
|
"X-CSRFToken": csrfToken,
|
||||||
if (data.success) {
|
},
|
||||||
hideLoading();
|
});
|
||||||
await updateFields(data.data);
|
const data = await response.json();
|
||||||
} else {
|
|
||||||
hideLoading();
|
if (data.success) {
|
||||||
Swal.fire("{% trans 'error' %}", data.error);
|
hideLoading();
|
||||||
}
|
await updateFields(data.data);
|
||||||
} catch (error) {
|
} else {
|
||||||
console.error("Error decoding VIN:", error);
|
hideLoading();
|
||||||
hideLoading();
|
Swal.fire("{% trans 'error' %}", data.error);
|
||||||
Swal.fire("error", "{% trans 'An error occurred while decoding the VIN.' %}");
|
}
|
||||||
}
|
} catch (error) {
|
||||||
|
console.error("Error decoding VIN:", error);
|
||||||
|
hideLoading();
|
||||||
|
Swal.fire("error", "{% trans 'An error occurred while decoding the VIN.' %}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateFields(vinData) {
|
||||||
|
const elements = {
|
||||||
|
makeSelect: document.getElementById("{{ form.id_car_make.id_for_label }}"),
|
||||||
|
modelSelect: document.getElementById("{{ form.id_car_model.id_for_label }}"),
|
||||||
|
yearSelect: document.getElementById("{{ form.year.id_for_label }}"),
|
||||||
|
generationContainer: document.getElementById("generation-div")
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(vinData);
|
||||||
|
if (vinData.make_id && elements.makeSelect) {
|
||||||
|
elements.makeSelect.value = vinData.make_id;
|
||||||
|
document.getElementById("make-check").innerHTML = "✓";
|
||||||
|
await loadModels(vinData.make_id);
|
||||||
|
}
|
||||||
|
if (vinData.model_id && elements.modelSelect) {
|
||||||
|
elements.modelSelect.value = vinData.model_id;
|
||||||
|
document.getElementById("model-check").innerHTML = "✓";
|
||||||
|
await loadSeries(vinData.model_id, vinData.year);
|
||||||
|
}
|
||||||
|
if (vinData.year && elements.yearSelect) {
|
||||||
|
elements.yearSelect.value = vinData.year;
|
||||||
|
document.getElementById("year-check").innerHTML = "✓";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scanner functions
|
||||||
|
async function startScanner(videoElement) {
|
||||||
|
if (!codeReader) return;
|
||||||
|
|
||||||
|
codeReader
|
||||||
|
.decodeFromVideoDevice(null, videoElement, async (result, err) => {
|
||||||
|
if (result) {
|
||||||
|
document.getElementById("{{ form.vin.id_for_label }}").value = result.text;
|
||||||
|
closeModal();
|
||||||
|
await decodeVin();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateFields(vinData) {
|
|
||||||
console.log(vinData);
|
|
||||||
if (vinData.make_id) {
|
|
||||||
makeSelect.value = vinData.make_id;
|
|
||||||
document.getElementById("make-check").innerHTML = "✓";
|
|
||||||
await loadModels(vinData.make_id);
|
|
||||||
}
|
|
||||||
if (vinData.model_id) {
|
|
||||||
modelSelect.value = vinData.model_id;
|
|
||||||
document.getElementById("model-check").innerHTML = "✓";
|
|
||||||
|
|
||||||
await loadSeries(vinData.model_id, vinData.year);
|
|
||||||
}
|
|
||||||
if (vinData.year) {
|
|
||||||
yearSelect.value = vinData.year;
|
|
||||||
document.getElementById("year-check").innerHTML = "✓";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the scanner
|
|
||||||
async function startScanner() {
|
|
||||||
codeReader
|
|
||||||
.decodeFromVideoDevice(null, videoElement, async (result, err) => {
|
|
||||||
let res = await result;
|
|
||||||
if (result) {
|
|
||||||
vinInput.value = result.text;
|
|
||||||
closeModal();
|
|
||||||
await decodeVin();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(console.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
function captureAndOCR() {
|
|
||||||
const canvas = document.createElement("canvas");
|
|
||||||
const context = canvas.getContext("2d");
|
|
||||||
canvas.width = videoElement.videoWidth;
|
|
||||||
canvas.height = videoElement.videoHeight;
|
|
||||||
context.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
|
|
||||||
Tesseract.recognize(canvas.toDataURL("image/png"), "eng")
|
|
||||||
.then(({ data: { text } }) => {
|
|
||||||
const vin = text.match(/[A-HJ-NPR-Z0-9]{17}/);
|
|
||||||
if (vin) vinInput.value = vin[0];
|
|
||||||
closeModal();
|
|
||||||
decodeVin();
|
|
||||||
})
|
|
||||||
.catch((err) => console.error("OCR Error:", err));
|
|
||||||
}
|
|
||||||
|
|
||||||
function stopScanner() {
|
|
||||||
if (currentStream) {
|
|
||||||
currentStream.getTracks().forEach((track) => track.stop());
|
|
||||||
currentStream = null;
|
|
||||||
}
|
|
||||||
codeReader.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetDropdown(dropdown, placeholder) {
|
|
||||||
dropdown.innerHTML = `<option value="">${placeholder}</option>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadModels(makeId) {
|
|
||||||
resetDropdown(modelSelect, '{% trans "Select" %}');
|
|
||||||
const response = await fetch(`${ajaxUrl}?action=get_models&make_id=${makeId}`, {
|
|
||||||
headers: {
|
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
|
||||||
"X-CSRFToken": csrfToken,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
data.forEach((model) => {
|
|
||||||
const option = document.createElement("option");
|
|
||||||
option.value = model.id_car_model;
|
|
||||||
option.textContent = document.documentElement.lang === "en" ? model.name : model.arabic_name;
|
|
||||||
modelSelect.appendChild(option);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadSeries(modelId, year) {
|
|
||||||
resetDropdown(serieSelect, '{% trans "Select" %}');
|
|
||||||
resetDropdown(trimSelect, '{% trans "Select" %}');
|
|
||||||
specificationsContent.innerHTML = "";
|
|
||||||
|
|
||||||
const response = await fetch(`${ajaxUrl}?action=get_series&model_id=${modelId}&year=${year}`, {
|
|
||||||
headers: {
|
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
|
||||||
"X-CSRFToken": csrfToken,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
console.log(data)
|
|
||||||
data.forEach((serie) => {
|
|
||||||
|
|
||||||
const option = document.createElement("option");
|
|
||||||
option.value = serie.id_car_serie;
|
|
||||||
option.textContent = document.documentElement.lang === "en" ? serie.name : serie.name;
|
|
||||||
generationContainer.innerHTML = serie.generation_name
|
|
||||||
serieSelect.appendChild(option);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadTrims(serie_id, model_id) {
|
|
||||||
resetDropdown(trimSelect, '{% trans "Select" %}');
|
|
||||||
specificationsContent.innerHTML = "";
|
|
||||||
const response = await fetch(`${ajaxUrl}?action=get_trims&serie_id=${serie_id}&model_id=${model_id}`, {
|
|
||||||
headers: {
|
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
|
||||||
"X-CSRFToken": csrfToken,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
data.forEach((trim) => {
|
|
||||||
const option = document.createElement("option");
|
|
||||||
option.value = trim.id_car_trim;
|
|
||||||
option.textContent = document.documentElement.lang === "en" ? trim.name : trim.name;
|
|
||||||
trimSelect.appendChild(option);
|
|
||||||
});
|
|
||||||
showSpecificationButton.disabled = !this.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadEquipment(trimId){
|
|
||||||
resetDropdown(equipmentSelect, '{% trans "Select" %}');
|
|
||||||
optionsContent.innerHTML = "";
|
|
||||||
const response = await fetch(`${ajaxUrl}?action=get_equipments&trim_id=${trimId}`, {
|
|
||||||
headers: {
|
|
||||||
'X-Requested-With': 'XMLHttpRequest',
|
|
||||||
'X-CSRFToken': csrfToken
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
data.forEach((equipment) => {
|
|
||||||
const option = document.createElement('option');
|
|
||||||
option.value = equipment.id_car_equipment;
|
|
||||||
option.textContent = equipment.name;
|
|
||||||
equipmentSelect.appendChild(option);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadSpecifications(trimId) {
|
|
||||||
specificationsContent.innerHTML = "";
|
|
||||||
const response = await fetch(`${ajaxUrl}?action=get_specifications&trim_id=${trimId}`, {
|
|
||||||
headers: {
|
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
|
||||||
"X-CSRFToken": csrfToken,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
data.forEach((spec) => {
|
|
||||||
const parentDiv = document.createElement("div");
|
|
||||||
parentDiv.innerHTML = `<strong>${spec.parent_name}</strong>`;
|
|
||||||
spec.specifications.forEach((s) => {
|
|
||||||
const specDiv = document.createElement("div");
|
|
||||||
specDiv.innerHTML = `• ${s.s_name}: ${s.s_value} ${s.s_unit}`;
|
|
||||||
parentDiv.appendChild(specDiv);
|
|
||||||
});
|
|
||||||
specificationsContent.appendChild(parentDiv);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadOptions(equipmentId) {
|
|
||||||
optionsContent.innerHTML = "";
|
|
||||||
const response = await fetch(`${ajaxUrl}?action=get_options&equipment_id=${equipmentId}`, {
|
|
||||||
headers: {
|
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
|
||||||
"X-CSRFToken": csrfToken,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
data.forEach((parent) => {
|
|
||||||
const parentDiv = document.createElement("div");
|
|
||||||
parentDiv.innerHTML = `<strong>${parent.parent_name}</strong>`;
|
|
||||||
parent.options.forEach((option) => {
|
|
||||||
const optDiv = document.createElement("div");
|
|
||||||
optDiv.innerHTML = `• ${option.option_name}`;
|
|
||||||
parentDiv.appendChild(optDiv);
|
|
||||||
});
|
|
||||||
optionsContent.appendChild(parentDiv);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
scanVinBtn.addEventListener("click", () => {
|
|
||||||
resultDisplay.textContent = "";
|
|
||||||
startScanner();
|
|
||||||
});
|
|
||||||
|
|
||||||
fallbackButton.addEventListener("click", () => {
|
|
||||||
captureAndOCR();
|
|
||||||
})
|
|
||||||
|
|
||||||
serieSelect.addEventListener("change", () => {
|
|
||||||
const serie_id = serieSelect.value;
|
|
||||||
const model_id = modelSelect.value;
|
|
||||||
if (serie_id && model_id) loadTrims(serie_id, model_id);
|
|
||||||
})
|
|
||||||
|
|
||||||
trimSelect.addEventListener("change", () => {
|
|
||||||
const trimId = trimSelect.value
|
|
||||||
showSpecificationButton.disabled = !trimId
|
|
||||||
showEquipmentButton.disabled = !trimId
|
|
||||||
if (trimId) loadSpecifications(trimId)
|
|
||||||
loadEquipment(trimId)
|
|
||||||
})
|
|
||||||
|
|
||||||
equipmentSelect.addEventListener("change", () => {
|
|
||||||
const equipmentId = equipmentSelect.value
|
|
||||||
if (equipmentId) loadOptions(equipmentId)
|
|
||||||
})
|
|
||||||
|
|
||||||
closeButton.addEventListener("click", closeModal);
|
|
||||||
makeSelect.addEventListener("change", (e) => {
|
|
||||||
loadModels(e.target.value, modelSelect.value);
|
|
||||||
})
|
|
||||||
modelSelect.addEventListener("change", (e) => {
|
|
||||||
loadSeries(e.target.value, yearSelect.value);
|
|
||||||
})
|
|
||||||
decodeVinBtn.addEventListener("click", decodeVin);
|
|
||||||
})
|
})
|
||||||
|
.catch(console.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
function captureAndOCR(videoElement, vinInput) {
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
const context = canvas.getContext("2d");
|
||||||
|
canvas.width = videoElement.videoWidth;
|
||||||
|
canvas.height = videoElement.videoHeight;
|
||||||
|
context.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
function showLoading() {
|
if (typeof Tesseract !== 'undefined') {
|
||||||
Swal.fire({
|
Tesseract.recognize(canvas.toDataURL("image/png"), "eng")
|
||||||
title: "{% trans 'Please Wait' %}",
|
.then(({ data: { text } }) => {
|
||||||
text: "{% trans 'Loading' %}...",
|
const vin = text.match(/[A-HJ-NPR-Z0-9]{17}/);
|
||||||
allowOutsideClick: false,
|
if (vin) vinInput.value = vin[0];
|
||||||
didOpen: () => {
|
closeModal();
|
||||||
Swal.showLoading();
|
decodeVin();
|
||||||
},
|
})
|
||||||
});
|
.catch((err) => console.error("OCR Error:", err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeModal(scanVinBtn) {
|
||||||
|
stopScanner();
|
||||||
|
try {
|
||||||
|
const scannerModal = document.getElementById("scannerModal");
|
||||||
|
if (scannerModal) {
|
||||||
|
document.activeElement.blur();
|
||||||
|
scannerModal.setAttribute("inert", "true");
|
||||||
|
const modalInstance = bootstrap.Modal.getInstance(scannerModal);
|
||||||
|
if (modalInstance) modalInstance.hide();
|
||||||
|
if (scanVinBtn) scanVinBtn.focus();
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error closing scanner modal:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function hideLoading() {
|
function stopScanner() {
|
||||||
Swal.close();
|
if (currentStream) {
|
||||||
}
|
currentStream.getTracks().forEach((track) => track.stop());
|
||||||
|
currentStream = null;
|
||||||
|
}
|
||||||
|
if (codeReader) codeReader.reset();
|
||||||
|
}
|
||||||
|
|
||||||
function notify(tag, msg) {
|
// Data loading functions
|
||||||
Swal.fire({
|
function resetDropdown(dropdown, placeholder) {
|
||||||
icon: tag,
|
if (dropdown) dropdown.innerHTML = `<option value="">${placeholder}</option>`;
|
||||||
titleText: msg,
|
}
|
||||||
});
|
|
||||||
|
async function loadModels(makeId, modelSelect, elements) {
|
||||||
|
if (!modelSelect) return;
|
||||||
|
|
||||||
|
resetDropdown(modelSelect, '{% trans "Select" %}');
|
||||||
|
const response = await fetch(`${ajaxUrl}?action=get_models&make_id=${makeId}`, {
|
||||||
|
headers: {
|
||||||
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
|
"X-CSRFToken": csrfToken,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
data.forEach((model) => {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = model.id_car_model;
|
||||||
|
option.textContent = document.documentElement.lang === "en" ? model.name : model.arabic_name;
|
||||||
|
modelSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadSeries(modelId, year, elements) {
|
||||||
|
if (!elements?.serieSelect) return;
|
||||||
|
|
||||||
|
resetDropdown(elements.serieSelect, '{% trans "Select" %}');
|
||||||
|
resetDropdown(elements.trimSelect, '{% trans "Select" %}');
|
||||||
|
if (elements.specificationsContent) elements.specificationsContent.innerHTML = "";
|
||||||
|
|
||||||
|
const response = await fetch(`${ajaxUrl}?action=get_series&model_id=${modelId}&year=${year}`, {
|
||||||
|
headers: {
|
||||||
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
|
"X-CSRFToken": csrfToken,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
data.forEach((serie) => {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = serie.id_car_serie;
|
||||||
|
option.textContent = document.documentElement.lang === "en" ? serie.name : serie.name;
|
||||||
|
if (elements.generationContainer) elements.generationContainer.innerHTML = serie.generation_name;
|
||||||
|
elements.serieSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadTrims(serie_id, model_id, elements) {
|
||||||
|
if (!elements?.trimSelect) return;
|
||||||
|
|
||||||
|
resetDropdown(elements.trimSelect, '{% trans "Select" %}');
|
||||||
|
if (elements.specificationsContent) elements.specificationsContent.innerHTML = "";
|
||||||
|
|
||||||
|
const response = await fetch(`${ajaxUrl}?action=get_trims&serie_id=${serie_id}&model_id=${model_id}`, {
|
||||||
|
headers: {
|
||||||
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
|
"X-CSRFToken": csrfToken,
|
||||||
}
|
}
|
||||||
</script>
|
});
|
||||||
{% endblock %}
|
const data = await response.json();
|
||||||
|
|
||||||
|
data.forEach((trim) => {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = trim.id_car_trim;
|
||||||
|
option.textContent = document.documentElement.lang === "en" ? trim.name : trim.name;
|
||||||
|
elements.trimSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (elements.showSpecificationButton) {
|
||||||
|
elements.showSpecificationButton.disabled = !elements.trimSelect.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadEquipment(trimId, elements) {
|
||||||
|
if (!elements?.equipmentSelect) return;
|
||||||
|
|
||||||
|
resetDropdown(elements.equipmentSelect, '{% trans "Select" %}');
|
||||||
|
if (elements.optionsContent) elements.optionsContent.innerHTML = "";
|
||||||
|
|
||||||
|
const response = await fetch(`${ajaxUrl}?action=get_equipments&trim_id=${trimId}`, {
|
||||||
|
headers: {
|
||||||
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
|
'X-CSRFToken': csrfToken
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
data.forEach((equipment) => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = equipment.id_car_equipment;
|
||||||
|
option.textContent = equipment.name;
|
||||||
|
elements.equipmentSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadSpecifications(trimId, elements) {
|
||||||
|
if (!elements?.specificationsContent) return;
|
||||||
|
|
||||||
|
elements.specificationsContent.innerHTML = "";
|
||||||
|
const response = await fetch(`${ajaxUrl}?action=get_specifications&trim_id=${trimId}`, {
|
||||||
|
headers: {
|
||||||
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
|
"X-CSRFToken": csrfToken,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
data.forEach((spec) => {
|
||||||
|
const parentDiv = document.createElement("div");
|
||||||
|
parentDiv.innerHTML = `<strong>${spec.parent_name}</strong>`;
|
||||||
|
spec.specifications.forEach((s) => {
|
||||||
|
const specDiv = document.createElement("div");
|
||||||
|
specDiv.innerHTML = `• ${s.s_name}: ${s.s_value} ${s.s_unit}`;
|
||||||
|
parentDiv.appendChild(specDiv);
|
||||||
|
});
|
||||||
|
elements.specificationsContent.appendChild(parentDiv);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadOptions(equipmentId, elements) {
|
||||||
|
if (!elements?.optionsContent) return;
|
||||||
|
|
||||||
|
elements.optionsContent.innerHTML = "";
|
||||||
|
const response = await fetch(`${ajaxUrl}?action=get_options&equipment_id=${equipmentId}`, {
|
||||||
|
headers: {
|
||||||
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
|
"X-CSRFToken": csrfToken,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
data.forEach((parent) => {
|
||||||
|
const parentDiv = document.createElement("div");
|
||||||
|
parentDiv.innerHTML = `<strong>${parent.parent_name}</strong>`;
|
||||||
|
parent.options.forEach((option) => {
|
||||||
|
const optDiv = document.createElement("div");
|
||||||
|
optDiv.innerHTML = `• ${option.option_name}`;
|
||||||
|
parentDiv.appendChild(optDiv);
|
||||||
|
});
|
||||||
|
elements.optionsContent.appendChild(parentDiv);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI Helper functions
|
||||||
|
function showLoading() {
|
||||||
|
Swal.fire({
|
||||||
|
title: "{% trans 'Please Wait' %}",
|
||||||
|
text: "{% trans 'Loading' %}...",
|
||||||
|
allowOutsideClick: false,
|
||||||
|
didOpen: () => {
|
||||||
|
Swal.showLoading();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideLoading() {
|
||||||
|
Swal.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function notify(tag, msg) {
|
||||||
|
Swal.fire({
|
||||||
|
icon: tag,
|
||||||
|
titleText: msg,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock customJS %}
|
||||||
Loading…
x
Reference in New Issue
Block a user