update
This commit is contained in:
parent
bb10d9186e
commit
e84428263a
18
.idea/dataSources.xml
generated
18
.idea/dataSources.xml
generated
@ -1,14 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="Django default" uuid="18477c7f-9c08-45c1-bea0-3ceeab27df15">
|
||||
<driver-ref>postgresql</driver-ref>
|
||||
<data-source source="LOCAL" name="db" uuid="3573a7a1-f2cc-4e7d-8280-f91580f8eeac">
|
||||
<driver-ref>sqlite.xerial</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<imported>true</imported>
|
||||
<remarks>$PROJECT_DIR$/car_inventory/settings.py</remarks>
|
||||
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:postgresql://127.0.0.1:5432/murad_haikal</jdbc-url>
|
||||
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
|
||||
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/db.sqlite3</jdbc-url>
|
||||
<working-dir>$ProjectFileDir$</working-dir>
|
||||
<libraries>
|
||||
<library>
|
||||
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0.jar</url>
|
||||
</library>
|
||||
<library>
|
||||
<url>file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar</url>
|
||||
</library>
|
||||
</libraries>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -14,7 +14,7 @@ admin.site.register(models.CarColors)
|
||||
admin.site.register(models.CarRegistration)
|
||||
admin.site.register(models.CustomCard)
|
||||
admin.site.register(models.CarSpecificationValue)
|
||||
|
||||
admin.site.register(models.CarEquipment)
|
||||
admin.site.register(models.CarOptionValue)
|
||||
admin.site.register(models.ExteriorColors)
|
||||
admin.site.register(models.InteriorColors)
|
||||
|
||||
@ -36,10 +36,10 @@ def decodevin(vin):
|
||||
|
||||
if result:=decode_vin(vin):
|
||||
return result
|
||||
elif result:=decode_vin_haikalna(vin):
|
||||
return result
|
||||
elif result:=elm(vin):
|
||||
return result
|
||||
elif result:=decode_vin_haikalna(vin):
|
||||
return result
|
||||
|
||||
else:
|
||||
return None
|
||||
|
||||
@ -116,7 +116,7 @@ def create_ledger_entity(sender, instance, created, **kwargs):
|
||||
# Create Cash Account
|
||||
asset_ca_cash = entity.create_account(
|
||||
coa_model=coa,
|
||||
code="1010",
|
||||
code="1101",
|
||||
role=roles.ASSET_CA_CASH,
|
||||
name=_("Cash"),
|
||||
balance_type="debit",
|
||||
|
||||
@ -244,6 +244,8 @@ class AjaxHandlerView(LoginRequiredMixin, View):
|
||||
"get_series": self.get_series,
|
||||
"get_trims": self.get_trims,
|
||||
"get_specifications": self.get_specifications,
|
||||
"get_equipments": self.get_equipments,
|
||||
"get_options": self.get_options,
|
||||
}
|
||||
handler = handlers.get(action)
|
||||
if handler:
|
||||
@ -314,17 +316,18 @@ class AjaxHandlerView(LoginRequiredMixin, View):
|
||||
|
||||
# Validate inputs
|
||||
if not model_id or not year:
|
||||
return JsonResponse(
|
||||
{"error": "Missing required parameters: model_id or year"}, status=400
|
||||
)
|
||||
|
||||
return JsonResponse({"error": "Missing required parameters: model_id or year"}, status=400)
|
||||
try:
|
||||
year = int(year)
|
||||
except ValueError:
|
||||
return JsonResponse({"error": "Invalid year format"}, status=400)
|
||||
|
||||
series = models.CarSerie.objects.filter(id_car_model=model_id).values(
|
||||
"id_car_serie", "name", "arabic_name"
|
||||
series = models.CarSerie.objects.filter(
|
||||
id_car_model=model_id,
|
||||
year_begin__lte=year,
|
||||
year_end__gte=year
|
||||
).values(
|
||||
"id_car_serie", "name", "arabic_name", "generation_name"
|
||||
)
|
||||
|
||||
return JsonResponse(list(series), safe=False)
|
||||
@ -373,6 +376,38 @@ class AjaxHandlerView(LoginRequiredMixin, View):
|
||||
]
|
||||
return JsonResponse(serialized_specs, safe=False)
|
||||
|
||||
def get_equipments(self, request):
|
||||
trim_id = request.GET.get('trim_id')
|
||||
equipments = models.CarEquipment.objects.filter(
|
||||
id_car_trim=trim_id
|
||||
).values('id_car_equipment', 'name').order_by('name')
|
||||
return JsonResponse(list(equipments), safe=False)
|
||||
|
||||
def get_options(self, request):
|
||||
equipment_id = request.GET.get('equipment_id')
|
||||
car_option_values = models.CarOptionValue.objects.filter(id_car_equipment=equipment_id)
|
||||
|
||||
options_by_parent = {}
|
||||
for value in car_option_values:
|
||||
option = value.id_car_option
|
||||
parent = option.id_parent
|
||||
parent_id = parent.id_car_option if parent else 0
|
||||
parent_name = parent.name if parent else "Root"
|
||||
if parent_id not in options_by_parent:
|
||||
options_by_parent[parent_id] = {'parent_name': parent_name, 'options': []}
|
||||
option_data = {
|
||||
'option_id': option.id_car_option,
|
||||
'option_name': option.name,
|
||||
'is_base': value.is_base,
|
||||
'equipment_name': value.id_car_equipment.name
|
||||
}
|
||||
options_by_parent[parent_id]['options'].append(option_data)
|
||||
serialized_options = [
|
||||
{'parent_name': v['parent_name'], 'options': v['options']}
|
||||
for v in options_by_parent.values()
|
||||
]
|
||||
return JsonResponse(serialized_options, safe=False)
|
||||
|
||||
|
||||
class CarInventory(LoginRequiredMixin, ListView):
|
||||
model = models.Car
|
||||
|
||||
BIN
static/.DS_Store
vendored
BIN
static/.DS_Store
vendored
Binary file not shown.
BIN
static/images/.DS_Store
vendored
BIN
static/images/.DS_Store
vendored
Binary file not shown.
BIN
static/images/car_make/Peugeot_S4LPdsy.png
Normal file
BIN
static/images/car_make/Peugeot_S4LPdsy.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
BIN
static/images/logos/.DS_Store
vendored
BIN
static/images/logos/.DS_Store
vendored
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 40 KiB |
@ -82,11 +82,7 @@
|
||||
{% 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 %}
|
||||
{{ form.id_car_make|add_class:"form-select form-select-sm" }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -104,11 +100,7 @@
|
||||
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 class="badge badge-phoenix fs-11 badge-phoenix-success m-1" id="generation-div"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -125,11 +117,7 @@
|
||||
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>
|
||||
@ -146,11 +134,20 @@
|
||||
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 %}
|
||||
<button type="button"
|
||||
class="btn btn-phoenix-primary m-1"
|
||||
id="specification-btn"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#specificationsModal"
|
||||
disabled>{% trans 'specifications'|capfirst %}
|
||||
</button>
|
||||
<button type="button"
|
||||
class="btn btn-phoenix-warning m-1"
|
||||
id="options-btn"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#equipmentOptionsModal"
|
||||
disabled>{% trans 'options'|capfirst %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -174,6 +171,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stock Type Card -->
|
||||
<div class="col-lg-4 col-xl-3">
|
||||
<div class="card h-100">
|
||||
@ -204,6 +202,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Receiving Date Field -->
|
||||
<div class="col-lg-4 col-xl-3">
|
||||
<div class="card h-100 border-1 rounded shadow">
|
||||
@ -223,6 +222,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Remarks Card -->
|
||||
<div class="col-lg-4 col-xl-3">
|
||||
<div class="card h-100">
|
||||
@ -243,21 +243,13 @@
|
||||
<!-- 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>
|
||||
@ -284,6 +276,40 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- equipments and options Modal -->
|
||||
<div class="modal fade" id="equipmentOptionsModal" tabindex="-1" aria-labelledby="equipmentOptionsModalLabel">
|
||||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||||
<div class="modal-content rounded-top-2">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="equipmentOptionsModalLabel">
|
||||
{% 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 class="col-lg-4 col-xl-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-body" id="equipment-container">
|
||||
<label class="form-label" for="equipment_id">
|
||||
{% trans 'equipment'|capfirst %}:
|
||||
</label>
|
||||
<select class="form-select form-select-sm"
|
||||
id="equipment_id"
|
||||
name="equipment_name_id">
|
||||
<option value="">{% trans 'Select' %}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="optionsContent"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-outline-primary" type="button" data-bs-dismiss="modal">{% trans 'Close' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scanner Modal -->
|
||||
<div class="modal fade" id="scannerModal" tabindex="-1" aria-labelledby="scannerModalLabel">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
@ -329,8 +355,12 @@
|
||||
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 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' %}";
|
||||
|
||||
@ -478,9 +508,11 @@
|
||||
});
|
||||
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;
|
||||
generationContainer.innerHTML = serie.generation_name
|
||||
serieSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
@ -492,7 +524,7 @@
|
||||
headers: {
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
"X-CSRFToken": csrfToken,
|
||||
},
|
||||
}
|
||||
});
|
||||
const data = await response.json();
|
||||
data.forEach((trim) => {
|
||||
@ -504,6 +536,24 @@
|
||||
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}`, {
|
||||
@ -525,6 +575,28 @@
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
@ -532,29 +604,37 @@
|
||||
|
||||
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);
|
||||
});
|
||||
const trimId = trimSelect.value
|
||||
showSpecificationButton.disabled = !trimId
|
||||
showEquipmentButton.disabled = !trimId
|
||||
if (trimId) loadSpecifications(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);
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
function showLoading() {
|
||||
Swal.fire({
|
||||
title: "{% trans 'Please Wait' %}",
|
||||
|
||||
675
templates/inventory/car_form_qabl alfalsafa.html
Normal file
675
templates/inventory/car_form_qabl alfalsafa.html
Normal file
@ -0,0 +1,675 @@
|
||||
{% extends "base.html" %} {% load i18n static 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-lg">
|
||||
<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-phoenix-warning fs-8 rounded-start"
|
||||
id="scan-vin-btn"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#scannerModal">
|
||||
<span class="fas fa-camera"></span>
|
||||
</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-phoenix-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">
|
||||
<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">
|
||||
<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">
|
||||
<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">
|
||||
<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 class="badge-phoenix-primary" id="generation-div"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Trims Card -->
|
||||
<div class="col-lg-4 col-xl-3">
|
||||
<div class="card h-100">
|
||||
<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">
|
||||
<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">
|
||||
<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">
|
||||
<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">
|
||||
<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>
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-danger me-1"
|
||||
id="options-btn"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#equipmentOptionsModal"
|
||||
disabled>{% trans 'options'|capfirst %}
|
||||
</button>
|
||||
<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>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<!-- Specification Modal -->
|
||||
<div class="modal fade" id="specificationsModal" tabindex="-1" aria-labelledby="specificationsModalLabel">
|
||||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||||
<div class="modal-content rounded-top-2">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" 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 class="modal-footer">
|
||||
<button class="btn btn-outline-primary" type="button" data-bs-dismiss="modal">{% trans 'Close' %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- equipments and options Modal -->
|
||||
<div class="modal fade" id="equipmentOptionsModal" tabindex="-1" aria-labelledby="equipmentOptionsModalLabel">
|
||||
<div class="modal-dialog modal-lg modal-dialog-scrollable">
|
||||
<div class="modal-content rounded-top-2">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="equipmentOptionsModalLabel">
|
||||
{% 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 class="col-lg-4 col-xl-3">
|
||||
<div class="card h-100">
|
||||
<div class="card-body" id="equipment-container">
|
||||
<label class="form-label" for="equipment_id">
|
||||
{% trans 'equipment'|capfirst %}:
|
||||
</label>
|
||||
<select class="form-select form-select-sm"
|
||||
id="equipment_id"
|
||||
name="equipment_name_id">
|
||||
<option value="">{% trans 'Select' %}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="optionsContent"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-outline-primary" type="button" data-bs-dismiss="modal">{% trans 'Close' %}</button>
|
||||
</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 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 -->
|
||||
</div>
|
||||
|
||||
<script>
|
||||
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;
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const csrfToken = getCookie("token");
|
||||
|
||||
const vinInput = document.getElementById("{{ form.vin.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 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' %}";
|
||||
|
||||
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) {
|
||||
Swal.fire("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();
|
||||
Swal.fire("{% trans 'error' %}", data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error decoding VIN:", error);
|
||||
hideLoading();
|
||||
Swal.fire("error", "{% 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 = "✓";
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
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)
|
||||
})
|
||||
|
||||
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);
|
||||
})
|
||||
|
||||
|
||||
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 %}
|
||||
@ -30,9 +30,9 @@
|
||||
<div class="col-12 col-sm-auto ">
|
||||
<div class="avatar avatar-3xl avatar-bordered mb-3">
|
||||
{% if cars.first.id_car_make.logo %}
|
||||
<img class="rounded-circle" src="{{ cars.first.id_car_make.logo.url }}" alt="">
|
||||
<img class="rounded" src="{{ cars.first.id_car_make.logo.url }}" alt="">
|
||||
{% else %}
|
||||
<img class="rounded-circle" src="{% static 'images/logos/car_make/sedan.svg' %}" alt="">
|
||||
<img class="rounded" src="{% static 'images/logos/car_make/sedan.svg' %}" alt="">
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user