635 lines
28 KiB
HTML
635 lines
28 KiB
HTML
{% extends "base.html" %}
|
|
{% load static %}
|
|
{% load i18n %}
|
|
{% load custom_filters %}
|
|
|
|
{% block content %}
|
|
<style>
|
|
#video {
|
|
width: 100%;
|
|
max-width: 480px;
|
|
height: auto;
|
|
margin: 0 auto;
|
|
}
|
|
.modal-dialog {
|
|
max-width: 95%;
|
|
}
|
|
|
|
</style>
|
|
{% include 'partials/form_errors.html' %}
|
|
|
|
<!-- JavaScript Section -->
|
|
<script src="https://unpkg.com/@zxing/library@latest"></script>
|
|
<script src='https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js'></script>
|
|
|
|
|
|
<div class="container p-2">
|
|
|
|
<!-- Specification Modal -->
|
|
<div class="modal fade" id="specificationsModal"
|
|
tabindex="-1"
|
|
aria-labelledby="specificationsModalLabel">
|
|
<div class="modal-dialog modal-xl modal-dialog-scrollable ">
|
|
<div class="modal-content rounded-top-2">
|
|
<div class="modal-header bg-success">
|
|
<h5 class="modal-title text-light"
|
|
id="specificationsModalLabel">
|
|
{% trans 'specifications'|capfirst %}
|
|
</h5>
|
|
<button type="button"
|
|
class="btn-close"
|
|
data-bs-dismiss="modal"
|
|
aria-label="{% trans 'Close' %}">
|
|
</button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<div id="specificationsContent"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Scanner Modal -->
|
|
<div class="modal fade" id="scannerModal" tabindex="-1" aria-labelledby="scannerModalLabel">
|
|
<div class="modal-dialog modal-dialog-centered">
|
|
<div class="modal-content rounded-top-3">
|
|
<div class="modal-header bg-primary text-white rounded-top-3 shadow">
|
|
<h5 class="modal-title" id="scannerModalLabel">{{ _("scanner") }}</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{{ _('Close') }}"></button>
|
|
</div>
|
|
<div class="modal-body text-center">
|
|
<video id="video" autoplay playsinline></video>
|
|
<p id="result" class="mt-2">{{ _("VIN will appear here.") }}</p>
|
|
<button id="ocr-fallback-btn" class="btn btn-primary mt-3">{{ _("Use OCR Fallback") }}</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- CAR FORM -->
|
|
|
|
<form method="post" id="carForm" class="form needs-validation" novalidate>
|
|
{% csrf_token %}
|
|
<div class="d-flex flex-column min-vh-100">
|
|
<div class="d-flex flex-column flex-sm-grow-1 ms-sm-14 p-4">
|
|
<main class="d-grid gap-4 p-1">
|
|
<div class="row g-4">
|
|
|
|
<!-- VIN -->
|
|
<div class="col-lg-12 col-xl-12">
|
|
<div class="card h-100 border-1 rounded shadow">
|
|
<div class="card-body">
|
|
<label class="form-label"
|
|
for="{{ form.vin.id_for_label }}">
|
|
{% trans 'VIN' %}:
|
|
</label>
|
|
<div class="input-group input-group-sm">
|
|
<button type="button"
|
|
class="btn btn-warning fs-6 rounded-start"
|
|
id="scan-vin-btn"
|
|
data-bs-toggle="modal"
|
|
data-bs-target="#scannerModal">
|
|
<i class="bi bi-camera-fill"></i>
|
|
</button>
|
|
<input type="text"
|
|
class="form-control form-control-sm"
|
|
id="{{ form.vin.id_for_label }}"
|
|
name="{{ form.vin.html_name }}">
|
|
<button type="button"
|
|
class="btn btn-sm btn-primary rounded-end"
|
|
id="decodeVinBtn">
|
|
{% trans 'Search' %}
|
|
</button>
|
|
</div>
|
|
{% if form.vin.errors %}
|
|
<div class="text-danger small">
|
|
{{ form.vin.errors|striptags }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Year Card -->
|
|
<div class="col-lg-4 col-xl-3">
|
|
<div class="card h-100 border-1 rounded shadow">
|
|
<div class="card-body" id="year-container">
|
|
|
|
<label class="form-label"
|
|
for="{{ form.year.id_for_label }}">
|
|
{% trans 'Year' %}:
|
|
</label>
|
|
<span class="text-success fw-bold" id="year-check"></span>
|
|
<input type="number"
|
|
class="form-control form-control-sm"
|
|
id="{{ form.year.id_for_label }}"
|
|
name="{{ form.year.html_name }}">
|
|
{% if form.year.errors %}
|
|
<div class="text-danger small">
|
|
{{ form.year.errors|striptags }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Makes Card -->
|
|
<div class="col-lg-4 col-xl-3">
|
|
<div class="card h-100 border-1 rounded shadow">
|
|
<div id="make-container" class="card-body">
|
|
<div class="status"></div>
|
|
<label class="form-label"
|
|
for="{{ form.id_car_make.id_for_label }}">
|
|
{% trans 'make'|capfirst %}:
|
|
</label>
|
|
<span class="text-success fw-bold" id="make-check"></span>
|
|
{{ form.id_car_make|add_class:"form-select form-select-sm" }}
|
|
{% if form.id_car_make.errors %}
|
|
<div class="text-danger small">
|
|
{{ form.id_car_make.errors|striptags }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Models Card -->
|
|
<div class="col-lg-4 col-xl-3">
|
|
<div class="card h-100 border-1 rounded shadow">
|
|
<div id="model-container" class="card-body">
|
|
<label class="form-label"
|
|
for="{{ form.id_car_model.id_for_label }}">
|
|
{% trans 'model'|capfirst %}:
|
|
</label>
|
|
<span class="text-success fw-bold" id="model-check"></span>
|
|
<select class="form-select form-select-sm"
|
|
id="{{ form.id_car_model.id_for_label }}"
|
|
name="{{ form.id_car_model.html_name }}">
|
|
<option value="">{% trans 'Select' %}</option>
|
|
</select>
|
|
{% if form.id_car_model.errors %}
|
|
<div class="text-danger small">
|
|
{{ form.id_car_model.errors|striptags }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Series Card -->
|
|
<div class="col-lg-4 col-xl-3">
|
|
<div class="card h-100 border-1 rounded shadow">
|
|
<div class="card-body" id="serie-container">
|
|
<label class="form-label"
|
|
for="{{ form.id_car_serie.id_for_label }}">
|
|
{% trans 'Series' %}:
|
|
</label>
|
|
<select class="form-select form-select-sm"
|
|
id="{{ form.id_car_serie.id_for_label }}"
|
|
name="{{ form.id_car_serie.html_name }}">
|
|
<option value="">{% trans 'Select' %}</option>
|
|
</select>
|
|
{% if form.id_car_serie.errors %}
|
|
<div class="text-danger small">
|
|
{{ form.id_car_serie.errors|striptags }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Trims Card -->
|
|
<div class="col-lg-4 col-xl-3">
|
|
<div class="card h-100 border-1 rounded shadow">
|
|
<div class="card-body" id="trim-container">
|
|
<label class="form-label"
|
|
for="{{ form.id_car_trim.id_for_label }}">
|
|
{% trans 'trim'|capfirst %}:
|
|
</label>
|
|
<select class="form-select form-select-sm"
|
|
id="{{ form.id_car_trim.id_for_label }}"
|
|
name="{{ form.id_car_trim.html_name }}">
|
|
<option value="">{% trans 'Select' %}</option>
|
|
</select>
|
|
{% if form.id_car_trim.errors %}
|
|
<div class="text-danger small">
|
|
{{ form.id_car_trim.errors|striptags }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row g-4">
|
|
|
|
|
|
<!-- Vendor Field -->
|
|
<div class="col-lg-4 col-xl-3">
|
|
<div class="card h-100 border-1 rounded shadow">
|
|
<div class="card-body">
|
|
<div class="form-group">
|
|
<label for="{{ form.vendor.id_for_label }}"
|
|
class="form-label">
|
|
{% trans 'Vendor' %}:
|
|
</label>
|
|
{{ form.vendor|add_class:"form-select form-select-sm" }}
|
|
</div>
|
|
{% if form.vendor.errors %}
|
|
<div class="text-danger small">
|
|
{{ form.vendor.errors|striptags }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Stock Type Card -->
|
|
<div class="col-lg-4 col-xl-3">
|
|
<div class="card h-100 border-1 rounded shadow">
|
|
<div class="card-body">
|
|
<label class="form-label"
|
|
for="{{ form.stock_type.id_for_label }}">
|
|
{% trans 'Stock Type'|capfirst %}:
|
|
</label>
|
|
{{ form.stock_type|add_class:"form-select form-select-sm" }}
|
|
{% if form.stock_type.errors %}
|
|
<div class="text-danger small">
|
|
{{ form.stock_type.errors|striptags }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Mileage Card -->
|
|
<div class="col-lg-4 col-xl-3">
|
|
<div class="card h-100 border-1 rounded shadow">
|
|
<div class="card-body">
|
|
<label class="form-label"
|
|
for="{{ form.mileage.id_for_label }}">
|
|
{% trans 'Mileage'|capfirst %}:
|
|
</label>
|
|
{{ form.mileage|add_class:"form-control form-control-sm" }}
|
|
{% if form.mileage.errors %}
|
|
<div class="text-danger small">
|
|
{{ form.mileage.errors|striptags }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Receiving Date Field -->
|
|
<div class="col-lg-4 col-xl-3">
|
|
<div class="card h-100 border-1 rounded shadow">
|
|
<div class="card-body">
|
|
<div class="form-group">
|
|
<label for="{{ form.receiving_date.id_for_label }}"
|
|
class="form-label">
|
|
{% trans 'Receiving Date' %}:
|
|
</label>
|
|
{{ form.receiving_date|add_class:"form-control form-control-sm" }}
|
|
{% if form.receiving_date.errors %}
|
|
<div class="text-danger small">
|
|
{{ form.receiving_date.errors|striptags }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Remarks Card -->
|
|
<div class="col-lg-4 col-xl-3">
|
|
<div class="card h-100 border-1 rounded shadow">
|
|
<div class="card-body">
|
|
<label class="label"
|
|
for="{{ form.remarks.id_for_label }}">
|
|
{% trans 'Remarks'|capfirst %}:
|
|
</label>
|
|
{{ form.remarks|add_class:"form-control form-control-sm" }}
|
|
{% if form.remarks.errors %}
|
|
<div class="text-danger small">
|
|
{{ form.remarks.errors|striptags }}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Specifications Buttons -->
|
|
<div class="row g-1">
|
|
<div class="btn-group">
|
|
<button type="button"
|
|
class="btn btn-sm btn-danger me-1"
|
|
id="specification-btn"
|
|
data-bs-toggle="modal"
|
|
data-bs-target="#specificationsModal"
|
|
disabled>{% trans 'specifications'|capfirst %}
|
|
</button>
|
|
<!--<div class="form-group">-->
|
|
<button type="submit"
|
|
name="add_another"
|
|
value="true"
|
|
class="btn btn-sm btn-success me-1">
|
|
{% trans "Save and Add Another" %}
|
|
</button>
|
|
<button type="submit"
|
|
name="go_to_stats"
|
|
value="true"
|
|
class="btn btn-sm btn-primary">
|
|
{% trans "Save and Go to Inventory" %}
|
|
</button>
|
|
<!--</div>-->
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<script>
|
|
|
|
document.addEventListener("DOMContentLoaded", function() {
|
|
|
|
function getCookie(name) {
|
|
let cookieValue = null;
|
|
if (document.cookie && document.cookie !== '') {
|
|
const cookies = document.cookie.split(';');
|
|
for (let cookie of cookies) {
|
|
cookie = cookie.trim();
|
|
if (cookie.substring(0, name.length + 1) === (name + '=')) {
|
|
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return cookieValue;
|
|
}
|
|
|
|
const csrfToken = getCookie('csrftoken');
|
|
|
|
const vinInput = document.getElementById('{{ form.vin.id_for_label }}');
|
|
const stockTypeSelect = document.getElementById('{{ form.stock_type.id_for_label }}');
|
|
const mileageInput = document.getElementById('{{ form.mileage.id_for_label }}');
|
|
const remarksInput = document.getElementById('{{ form.remarks.id_for_label }}');
|
|
const decodeVinBtn = document.getElementById('decodeVinBtn');
|
|
const makeSelect = document.getElementById('{{ form.id_car_make.id_for_label }}');
|
|
const modelSelect = document.getElementById('{{ form.id_car_model.id_for_label }}');
|
|
const yearSelect = document.getElementById('{{ form.year.id_for_label }}');
|
|
const serieSelect = document.getElementById('{{ form.id_car_serie.id_for_label }}');
|
|
const trimSelect = document.getElementById('{{ form.id_car_trim.id_for_label }}');
|
|
const showSpecificationButton = document.getElementById('specification-btn');
|
|
const specificationsContent = document.getElementById('specificationsContent');
|
|
const makeBg = document.getElementById('make-container');
|
|
const modelBg = document.getElementById('model-container');
|
|
const yearBg = document.getElementById('year-container');
|
|
const serieBg = document.getElementById('serie-container');
|
|
const trimBg = document.getElementById('trim-container');
|
|
/*const saveCarBtn = document.getElementById('saveCarBtn');*/
|
|
|
|
const ajaxUrl = "{% url 'ajax_handler' %}";
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
async function decodeVin() {
|
|
const vinNumber = vinInput.value.trim();
|
|
if (vinNumber.length !== 17) {
|
|
notify("error","{% trans 'Please enter a valid VIN.' %}");
|
|
/*alert("{% trans 'Please enter a valid VIN.' %}");*/
|
|
return;
|
|
}
|
|
showLoading();
|
|
try {
|
|
const response = await fetch(`${ajaxUrl}?action=decode_vin&vin_no=${vinNumber}`, {
|
|
headers: {
|
|
'X-Requested-With': 'XMLHttpRequest',
|
|
'X-CSRFToken': csrfToken
|
|
}
|
|
});
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
hideLoading();
|
|
await updateFields(data.data);
|
|
} else {
|
|
hideLoading();
|
|
notify("error",data.error)
|
|
/* alert(data.error || "{% trans 'Failed to decode VIN.' %}");*/
|
|
}
|
|
} catch (error) {
|
|
console.error("Error decoding VIN:", error);
|
|
hideLoading();
|
|
notify("error","{% trans 'An error occurred while decoding the VIN.' %}")
|
|
/*alert("{% trans 'An error occurred while decoding the VIN.' %}");*/
|
|
}
|
|
}
|
|
|
|
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 = '✓';
|
|
}
|
|
/*checkFormCompletion();*/
|
|
}
|
|
|
|
// 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>`;
|
|
}
|
|
/*
|
|
function checkFormCompletion() {
|
|
const isFormComplete = vinInput.value.length === 17 && stockTypeSelect.value;
|
|
saveCarBtn.disabled = !isFormComplete;
|
|
}
|
|
*/
|
|
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();
|
|
data.forEach((serie) => {
|
|
const option = document.createElement('option');
|
|
option.value = serie.id_car_serie;
|
|
option.textContent = document.documentElement.lang === 'en' ? serie.name : serie.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 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);
|
|
});
|
|
}
|
|
|
|
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;
|
|
if (trimId) loadSpecifications(trimId);
|
|
});
|
|
|
|
closeButton.addEventListener('click', closeModal);
|
|
/*stockTypeSelect.addEventListener('change', checkFormCompletion);
|
|
mileageInput.addEventListener('input', checkFormCompletion);
|
|
remarksInput.addEventListener('input', checkFormCompletion);*/
|
|
makeSelect.addEventListener("change", (e) => {loadModels(e.target.value, modelSelect.value)})
|
|
modelSelect.addEventListener("change", (e) => {loadSeries(e.target.value, yearSelect.value)})
|
|
decodeVinBtn.addEventListener('click', decodeVin);
|
|
});
|
|
const 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
|
|
});
|
|
}
|
|
</script>
|
|
|
|
{% endblock %} |