Merge branch 'main' of http://10.10.1.120:3000/tenhal_admin/haikal
This commit is contained in:
commit
faf5f14881
Binary file not shown.
Binary file not shown.
@ -1,5 +1,5 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path, include, re_path
|
from django.urls import path, include
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls.i18n import i18n_patterns
|
from django.conf.urls.i18n import i18n_patterns
|
||||||
|
|||||||
@ -1,83 +1,45 @@
|
|||||||
import re
|
from openai import OpenAI
|
||||||
from nltk.tokenize import word_tokenize
|
from inventory import models
|
||||||
from django.db.models import Q
|
from car_inventory import settings
|
||||||
from inventory.models import Car # Import your car-related models
|
|
||||||
import nltk
|
|
||||||
# Download required NLTK resources
|
|
||||||
try:
|
|
||||||
|
|
||||||
nltk.download("punkt")
|
def fetch_data(dealer):
|
||||||
except ImportError:
|
try:
|
||||||
raise ImportError("Ensure nltk is installed by running 'pip install nltk'.")
|
# Annotate total cars by make, model, and trim
|
||||||
|
cars = models.Car.objects.filter(dealer=dealer).count()
|
||||||
|
print(cars)
|
||||||
|
if cars:
|
||||||
|
return f"إجمالي عدد السيارات في المخزون الخاص بـ {dealer.get_local_name}) هو {cars}"
|
||||||
|
# return f"The total cars in {dealer} inventory is ( {cars} )."
|
||||||
|
else:
|
||||||
|
return "No cars found in the inventory."
|
||||||
|
except Exception as e:
|
||||||
|
return f"An error occurred while fetching car data: {str(e)}"
|
||||||
|
|
||||||
# Static responses for predefined intents
|
|
||||||
RESPONSES = {
|
|
||||||
"greet": ["Hello! How can I assist you today with Haikal Car Inventory?"],
|
|
||||||
"inventory_check": ["You can check the car inventory in the Cars section. Do you want to search for a specific car?"],
|
|
||||||
"car_status": ["If a car is sold, it will either be marked as SOLD or removed from the inventory, as per your preferences."],
|
|
||||||
"sell_process": ["To sell a car, the process involves creating a Sell Order, adding the customer, confirming payment, generating an invoice, and finally delivering the car."],
|
|
||||||
"transfer_process": ["Dealers can transfer cars to other branches or dealers for display or sale. This is handled through Sell Orders as well."],
|
|
||||||
"bye": ["Goodbye! If you need further assistance, just ask!"],
|
|
||||||
}
|
|
||||||
|
|
||||||
def clean_input(user_input):
|
def get_gpt4_response(user_input, dealer):
|
||||||
"""
|
dealer = dealer
|
||||||
Clean and tokenize user input.
|
client = OpenAI(api_key=settings.OPENAI_API_KEY)
|
||||||
"""
|
|
||||||
user_input = user_input.lower()
|
|
||||||
user_input = re.sub(r"[^\w\s]", "", user_input) # Remove punctuation
|
|
||||||
return word_tokenize(user_input)
|
|
||||||
|
|
||||||
def classify_input(tokens):
|
if "سيارة في المخزون" in user_input.lower():
|
||||||
"""
|
# cars = user_input.split("how many cars")[-1].strip()
|
||||||
Classify user intent based on tokens.
|
car_data = fetch_data(dealer)
|
||||||
"""
|
user_input += f" Relevant car data: {car_data}"
|
||||||
if any(word in tokens for word in ["hello", "hi", "hey"]):
|
try:
|
||||||
return "greet"
|
completion = client.chat.completions.create(
|
||||||
elif any(word in tokens for word in ["inventory", "cars", "check"]):
|
model="gpt-4o",
|
||||||
return "inventory_check"
|
messages=[
|
||||||
elif any(word in tokens for word in ["sell", "sold", "process"]):
|
{
|
||||||
return "sell_process"
|
"role": "system",
|
||||||
elif any(word in tokens for word in ["transfer", "branch", "display"]):
|
"content": (
|
||||||
return "transfer_process"
|
"You are an assistant specialized in car inventory management for the Haikal system. "
|
||||||
elif any(word in tokens for word in ["bye", "goodbye", "exit"]):
|
"You can answer questions about the inventory, sales process, car transfers, invoices, "
|
||||||
return "bye"
|
"and other application-specific functionalities. Always provide responses aligned "
|
||||||
elif any(word in tokens for word in ["price", "cost", "value"]):
|
"with the Haikal system's structure and features."
|
||||||
return "car_price"
|
)
|
||||||
else:
|
},
|
||||||
return "unknown"
|
{"role": "user", "content": user_input},
|
||||||
|
],
|
||||||
def get_dynamic_response(intent, tokens):
|
)
|
||||||
"""
|
return completion.choices[0].message.content.strip()
|
||||||
Generate dynamic responses by querying the database.
|
except Exception as e:
|
||||||
"""
|
return f"An error occurred while generating the response: {str(e)}"
|
||||||
if intent == "car_price":
|
|
||||||
# Extract car name from tokens
|
|
||||||
car_name = " ".join([word.capitalize() for word in tokens])
|
|
||||||
try:
|
|
||||||
car = Car.objects.filter(Q(make__icontains=car_name) | Q(model__icontains=car_name)).first()
|
|
||||||
if car:
|
|
||||||
return f"The price of {car_name} is {car.finance.selling_price}."
|
|
||||||
return f"Sorry, no car matching '{car_name}' was found in the inventory."
|
|
||||||
except Exception as e:
|
|
||||||
return f"An error occurred while retrieving the car price: {str(e)}"
|
|
||||||
return None
|
|
||||||
|
|
||||||
def get_response(user_input):
|
|
||||||
"""
|
|
||||||
Generate a response based on the user's input.
|
|
||||||
"""
|
|
||||||
tokens = clean_input(user_input)
|
|
||||||
intent = classify_input(tokens)
|
|
||||||
|
|
||||||
# Check for a dynamic response
|
|
||||||
dynamic_response = get_dynamic_response(intent, tokens)
|
|
||||||
if dynamic_response:
|
|
||||||
return dynamic_response
|
|
||||||
|
|
||||||
# Return a static response if available
|
|
||||||
if intent in RESPONSES:
|
|
||||||
return RESPONSES[intent][0]
|
|
||||||
|
|
||||||
# Default response for unknown intents
|
|
||||||
return "I'm sorry, I didn't understand that. Could you rephrase your question about the Haikal system?"
|
|
||||||
|
|||||||
@ -1,34 +1,25 @@
|
|||||||
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
from .chatbot_logic import get_response
|
from .chatbot_logic import get_gpt4_response
|
||||||
import json
|
import json
|
||||||
|
|
||||||
class ChatbotView(View):
|
class ChatbotView(LoginRequiredMixin, View):
|
||||||
"""
|
|
||||||
Class-based view to handle chatbot template rendering (GET)
|
|
||||||
and GPT-4-based chatbot API responses (POST).
|
|
||||||
"""
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
"""
|
|
||||||
Render the chatbot interface template.
|
|
||||||
"""
|
|
||||||
return render(request, "haikalbot/chatbot.html")
|
return render(request, "haikalbot/chatbot.html")
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
"""
|
dealer = request.user.dealer
|
||||||
Handle chatbot API requests and return responses as JSON.
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
# Parse JSON payload from the request body
|
|
||||||
data = json.loads(request.body)
|
data = json.loads(request.body)
|
||||||
user_message = data.get("message", "").strip()
|
user_message = data.get("message", "").strip()
|
||||||
|
|
||||||
if not user_message:
|
if not user_message:
|
||||||
return JsonResponse({"error": "Message cannot be empty."}, status=400)
|
return JsonResponse({"error": "Message cannot be empty."}, status=400)
|
||||||
|
|
||||||
# Get GPT-4 response
|
chatbot_response = get_gpt4_response(user_message, dealer)
|
||||||
chatbot_response = get_response(user_message)
|
|
||||||
|
|
||||||
return JsonResponse({"response": chatbot_response}, status=200)
|
return JsonResponse({"response": chatbot_response}, status=200)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -9,28 +9,31 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
def handle(self, *args, **kwargs):
|
def handle(self, *args, **kwargs):
|
||||||
client = OpenAI(api_key=settings.OPENAI_API_KEY)
|
client = OpenAI(api_key=settings.OPENAI_API_KEY)
|
||||||
car_make = CarMake.objects.all()
|
car_model = CarModel.objects.all()[0:1000]
|
||||||
total = car_make.count()
|
total = car_model.count()
|
||||||
print(f'Translating {total} names...')
|
print(f'Translating {total} names...')
|
||||||
for index, car_make in enumerate(car_make, start=1):
|
for index, car_model in enumerate(car_model, start=1):
|
||||||
try:
|
try:
|
||||||
completion = client.chat.completions.create(
|
completion = client.chat.completions.create(
|
||||||
model="gpt-4",
|
model="gpt-4o",
|
||||||
messages=[
|
messages=[
|
||||||
{
|
{
|
||||||
"role": "system",
|
"role": "system",
|
||||||
"content": "You are a translation assistant that translates English to Arabic."
|
"content": "You are an assistant that finds the Arabic Names for car models."
|
||||||
|
"Do not translate just write the arabic names."
|
||||||
|
"If the name is a number just write it as is."
|
||||||
|
"If you can't find the arabic name just write -"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"role": "user",
|
"role": "user",
|
||||||
"content": car_make.name
|
"content": car_model.name
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
temperature=0.3,
|
temperature=0.2,
|
||||||
)
|
)
|
||||||
translation = completion.choices[0].message.content.strip()
|
translation = completion.choices[0].message.content.strip()
|
||||||
car_make.arabic_name = translation
|
car_model.arabic_name = translation
|
||||||
car_make.save()
|
car_model.save()
|
||||||
print(f"[{index}/{total}] Translated '{car_make.name}' to '{translation}'")
|
print(f"[{index}/{total}] Translated '{car_model.name}' to '{translation}'")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error translating '{car_make.name}': {e}")
|
print(f"Error translating '{car_model.name}': {e}")
|
||||||
|
|||||||
@ -47,6 +47,9 @@ urlpatterns = [
|
|||||||
|
|
||||||
|
|
||||||
# Car URLs
|
# Car URLs
|
||||||
|
path('cars/inventory/',
|
||||||
|
views.CarInventory.as_view(),
|
||||||
|
name='car_inventory_all'),
|
||||||
path('cars/inventory/<int:make_id>/<int:model_id>/<int:trim_id>/',
|
path('cars/inventory/<int:make_id>/<int:model_id>/<int:trim_id>/',
|
||||||
views.CarInventory.as_view(),
|
views.CarInventory.as_view(),
|
||||||
name='car_inventory'),
|
name='car_inventory'),
|
||||||
|
|||||||
@ -296,11 +296,11 @@ class CarInventory(LoginRequiredMixin, ListView):
|
|||||||
ordering = ["receiving_date"]
|
ordering = ["receiving_date"]
|
||||||
|
|
||||||
def get_queryset(self, *args, **kwargs):
|
def get_queryset(self, *args, **kwargs):
|
||||||
query = self.request.GET.get("q")
|
query = self.request.GET.get('q')
|
||||||
make_id = self.kwargs["make_id"]
|
make_id = self.kwargs['make_id']
|
||||||
model_id = self.kwargs["model_id"]
|
model_id = self.kwargs['model_id']
|
||||||
trim_id = self.kwargs["trim_id"]
|
trim_id = self.kwargs['trim_id']
|
||||||
|
|
||||||
cars = models.Car.objects.filter(
|
cars = models.Car.objects.filter(
|
||||||
dealer=self.request.user.dealer.get_root_dealer,
|
dealer=self.request.user.dealer.get_root_dealer,
|
||||||
id_car_make=make_id,
|
id_car_make=make_id,
|
||||||
@ -355,32 +355,28 @@ def inventory_stats_view(request):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Prepare the nested structure
|
|
||||||
inventory = {}
|
inventory = {}
|
||||||
for car in cars:
|
for car in cars:
|
||||||
# Make Level
|
|
||||||
make = car.id_car_make
|
make = car.id_car_make
|
||||||
if make.id_car_make not in inventory:
|
if make.id_car_make not in inventory:
|
||||||
inventory[make.id_car_make] = {
|
inventory[make.id_car_make] = {
|
||||||
"make_id": make.id_car_make,
|
'make_id': make.id_car_make,
|
||||||
"make_name": make.get_local_name(),
|
'make_name': make.get_local_name(),
|
||||||
"total_cars": 0,
|
'total_cars': 0,
|
||||||
"models": {},
|
'models': {}
|
||||||
}
|
}
|
||||||
inventory[make.id_car_make]["total_cars"] += 1
|
inventory[make.id_car_make]["total_cars"] += 1
|
||||||
|
|
||||||
# Model Level
|
|
||||||
model = car.id_car_model
|
model = car.id_car_model
|
||||||
if model and model.id_car_model not in inventory[make.id_car_make]["models"]:
|
if model and model.id_car_model not in inventory[make.id_car_make]['models']:
|
||||||
inventory[make.id_car_make]["models"][model.id_car_model] = {
|
inventory[make.id_car_make]['models'][model.id_car_model] = {
|
||||||
"model_id": model.id_car_model,
|
'model_id': model.id_car_model,
|
||||||
"model_name": model.get_local_name(),
|
'model_name': model.get_local_name(),
|
||||||
"total_cars": 0,
|
'total_cars': 0,
|
||||||
"trims": {},
|
'trims': {}
|
||||||
}
|
}
|
||||||
inventory[make.id_car_make]["models"][model.id_car_model]["total_cars"] += 1
|
inventory[make.id_car_make]["models"][model.id_car_model]["total_cars"] += 1
|
||||||
|
|
||||||
# Trim Level
|
|
||||||
trim = car.id_car_trim
|
trim = car.id_car_trim
|
||||||
if (
|
if (
|
||||||
trim
|
trim
|
||||||
@ -394,15 +390,14 @@ def inventory_stats_view(request):
|
|||||||
trim.id_car_trim
|
trim.id_car_trim
|
||||||
]["total_cars"] += 1
|
]["total_cars"] += 1
|
||||||
|
|
||||||
# Convert to a list for easier template rendering
|
|
||||||
result = {
|
result = {
|
||||||
"total_cars": cars.count(),
|
"total_cars": cars.count(),
|
||||||
"makes": [
|
"makes": [
|
||||||
{
|
{
|
||||||
"make_id": make_data["make_id"],
|
'make_id': make_data['make_id'],
|
||||||
"make_name": make_data["make_name"],
|
'make_name': make_data['make_name'],
|
||||||
"total_cars": make_data["total_cars"],
|
'total_cars': make_data['total_cars'],
|
||||||
"models": [
|
'models': [
|
||||||
{
|
{
|
||||||
"model_id": model_data["model_id"],
|
"model_id": model_data["model_id"],
|
||||||
"model_name": model_data["model_name"],
|
"model_name": model_data["model_name"],
|
||||||
|
|||||||
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/.DS_Store
vendored
BIN
static/images/car_make/.DS_Store
vendored
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.0 KiB |
BIN
static/images/logos/.DS_Store
vendored
BIN
static/images/logos/.DS_Store
vendored
Binary file not shown.
@ -12,7 +12,6 @@
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
height: 200px;
|
height: 200px;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
background-color: #f9f9f9;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -346,7 +346,7 @@
|
|||||||
<script>
|
<script>
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
|
||||||
function getCookie(name) {
|
function getCookie(name) {
|
||||||
let cookieValue = null;
|
let cookieValue = null;
|
||||||
if (document.cookie && document.cookie !== '') {
|
if (document.cookie && document.cookie !== '') {
|
||||||
@ -363,7 +363,7 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const csrfToken = getCookie('csrftoken');
|
const csrfToken = getCookie('csrftoken');
|
||||||
|
|
||||||
const vinInput = document.getElementById('{{ form.vin.id_for_label }}');
|
const vinInput = document.getElementById('{{ form.vin.id_for_label }}');
|
||||||
const stockTypeSelect = document.getElementById('{{ form.stock_type.id_for_label }}');
|
const stockTypeSelect = document.getElementById('{{ form.stock_type.id_for_label }}');
|
||||||
const mileageInput = document.getElementById('{{ form.mileage.id_for_label }}');
|
const mileageInput = document.getElementById('{{ form.mileage.id_for_label }}');
|
||||||
@ -382,9 +382,9 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||||||
const serieBg = document.getElementById('serie-container');
|
const serieBg = document.getElementById('serie-container');
|
||||||
const trimBg = document.getElementById('trim-container');
|
const trimBg = document.getElementById('trim-container');
|
||||||
/*const saveCarBtn = document.getElementById('saveCarBtn');*/
|
/*const saveCarBtn = document.getElementById('saveCarBtn');*/
|
||||||
|
|
||||||
const ajaxUrl = "{% url 'ajax_handler' %}";
|
const ajaxUrl = "{% url 'ajax_handler' %}";
|
||||||
|
|
||||||
const closeButton = document.querySelector(".btn-close");
|
const closeButton = document.querySelector(".btn-close");
|
||||||
const scanVinBtn = document.getElementById("scan-vin-btn");
|
const scanVinBtn = document.getElementById("scan-vin-btn");
|
||||||
const videoElement = document.getElementById('video');
|
const videoElement = document.getElementById('video');
|
||||||
@ -393,7 +393,7 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||||||
let codeReader;
|
let codeReader;
|
||||||
codeReader = new ZXing.BrowserMultiFormatReader();
|
codeReader = new ZXing.BrowserMultiFormatReader();
|
||||||
let currentStream = null;
|
let currentStream = null;
|
||||||
|
|
||||||
function showLoading() {
|
function showLoading() {
|
||||||
Swal.fire({
|
Swal.fire({
|
||||||
title: "{% trans 'Please Wait' %}",
|
title: "{% trans 'Please Wait' %}",
|
||||||
@ -424,7 +424,7 @@ function closeModal(){
|
|||||||
console.error("Error closing scanner modal:", err);
|
console.error("Error closing scanner modal:", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function decodeVin() {
|
async function decodeVin() {
|
||||||
const vinNumber = vinInput.value.trim();
|
const vinNumber = vinInput.value.trim();
|
||||||
if (vinNumber.length !== 17) {
|
if (vinNumber.length !== 17) {
|
||||||
@ -475,10 +475,10 @@ async function updateFields(vinData) {
|
|||||||
}
|
}
|
||||||
/*checkFormCompletion();*/
|
/*checkFormCompletion();*/
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the scanner
|
// Start the scanner
|
||||||
async function startScanner() {
|
async function startScanner() {
|
||||||
codeReader.decodeFromVideoDevice(null, videoElement, async(result, err) => {
|
codeReader.decodeFromVideoDevice(null, videoElement, async(result, err) => {
|
||||||
let res = await result
|
let res = await result
|
||||||
if (result) {
|
if (result) {
|
||||||
vinInput.value = result.text;
|
vinInput.value = result.text;
|
||||||
@ -487,7 +487,7 @@ async function startScanner() {
|
|||||||
}
|
}
|
||||||
}).catch(console.error);
|
}).catch(console.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
function captureAndOCR() {
|
function captureAndOCR() {
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
const context = canvas.getContext('2d');
|
const context = canvas.getContext('2d');
|
||||||
@ -564,7 +564,7 @@ async function loadTrims(serie_id, model_id) {
|
|||||||
specificationsContent.innerHTML = '';
|
specificationsContent.innerHTML = '';
|
||||||
const response = await fetch(`${ajaxUrl}?action=get_trims&serie_id=${serie_id}&model_id=${model_id}`, {
|
const response = await fetch(`${ajaxUrl}?action=get_trims&serie_id=${serie_id}&model_id=${model_id}`, {
|
||||||
headers: {
|
headers: {
|
||||||
'X-Requested-With': 'XMLHttpRequest',
|
'X-Requested-With': 'XMLHttpRequest',
|
||||||
'X-CSRFToken': csrfToken
|
'X-CSRFToken': csrfToken
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -586,7 +586,7 @@ async function loadSpecifications(trimId){
|
|||||||
'X-CSRFToken': csrfToken
|
'X-CSRFToken': csrfToken
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
data.forEach((spec) => {
|
data.forEach((spec) => {
|
||||||
const parentDiv = document.createElement('div');
|
const parentDiv = document.createElement('div');
|
||||||
parentDiv.innerHTML = `<strong>${spec.parent_name}</strong>`;
|
parentDiv.innerHTML = `<strong>${spec.parent_name}</strong>`;
|
||||||
@ -603,23 +603,23 @@ async function loadSpecifications(trimId){
|
|||||||
resultDisplay.textContent = "";
|
resultDisplay.textContent = "";
|
||||||
startScanner();
|
startScanner();
|
||||||
});
|
});
|
||||||
|
|
||||||
fallbackButton.addEventListener('click', () => {
|
fallbackButton.addEventListener('click', () => {
|
||||||
captureAndOCR();
|
captureAndOCR();
|
||||||
});
|
});
|
||||||
|
|
||||||
serieSelect.addEventListener('change', () => {
|
serieSelect.addEventListener('change', () => {
|
||||||
const serie_id = serieSelect.value;
|
const serie_id = serieSelect.value;
|
||||||
const model_id = modelSelect.value;
|
const model_id = modelSelect.value;
|
||||||
if (serie_id && model_id) loadTrims(serie_id, model_id);
|
if (serie_id && model_id) loadTrims(serie_id, model_id);
|
||||||
});
|
});
|
||||||
|
|
||||||
trimSelect.addEventListener('change', () => {
|
trimSelect.addEventListener('change', () => {
|
||||||
const trimId = trimSelect.value;
|
const trimId = trimSelect.value;
|
||||||
showSpecificationButton.disabled = !trimId;
|
showSpecificationButton.disabled = !trimId;
|
||||||
if (trimId) loadSpecifications(trimId);
|
if (trimId) loadSpecifications(trimId);
|
||||||
});
|
});
|
||||||
|
|
||||||
closeButton.addEventListener('click', closeModal);
|
closeButton.addEventListener('click', closeModal);
|
||||||
/*stockTypeSelect.addEventListener('change', checkFormCompletion);
|
/*stockTypeSelect.addEventListener('change', checkFormCompletion);
|
||||||
mileageInput.addEventListener('input', checkFormCompletion);
|
mileageInput.addEventListener('input', checkFormCompletion);
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
{% for make in inventory.makes %}
|
{% for make in inventory.makes %}
|
||||||
<div class="accordion-item">
|
<div class="accordion-item">
|
||||||
<p class="accordion-header" id="heading{{ make.make_id }}">
|
<p class="accordion-header" id="heading{{ make.make_id }}">
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="accordion-button"
|
class="accordion-button"
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user