theme and plugin sync

This commit is contained in:
2024-03-20 17:38:18 -04:00
commit 03e44e02f0
224 changed files with 56542 additions and 0 deletions

BIN
autocart_assets/.DS_Store vendored Normal file

Binary file not shown.

12
autocart_assets/README.md Normal file
View File

@@ -0,0 +1,12 @@
Content Filter
=========
A slide-in filter panel powered by CSS and jQuery.
[Article on CodyHouse](http://codyhouse.co/gem/content-filter/)
[Demo](http://codyhouse.co/demo/content-filter/index.html)
Filter plugin: [MixItUp](https://github.com/patrickkunka/mixitup) (free to use in non-commercial projects)
[Terms](http://codyhouse.co/terms/)

View File

@@ -0,0 +1,97 @@
:root {
--mdc-theme-primary: #961d20;
--mdc-theme-secondary: #961d20;
--mdc-checkbox-checked-color: #961d20;
/* --mdc-ripple-press-opacity: #961d20; */
}
.range-label{
background-color: var(--gray-800);
padding: 0.3rem 1rem;
color: white;
position: absolute;
text-align: center;
border-radius: 4px;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
top: 35%;
left: 50%;
transform: translate(-50%, -100%);
}
.ripple {
overflow: hidden;
position: relative;
}
.ripple:before {
pointer-events: none;
background-image: radial-gradient(circle, #000 10%, transparent 10.01%);
background-repeat: no-repeat;
background-position: 50%;
transform: scale(10, 10);
transition: transform 0.5s, opacity 1s;
}
.ripple:hover:before {
transform: scale(0, 0);
opacity: 0.2;
}
.selected {
color: #961d20; /* Change this to your desired color */
}
.mdc-slider .mdc-slider__thumb-knob {
border: 2px solid #fff!important;
-webkit-box-shadow: 0 1px 4px rgba(0,0,0,.4)!important;
box-shadow: 0 1px 4px rgba(0,0,0,.4)!important;
/* background: var(--color-primary-dark)!important; */
border-radius: 50%;
}
li.autocart-filters:hover {
color: var(--mdc-theme-primary) !important;
}
/* .autocart-filters *:hover {
color: var(--mdc-theme-primary)
} */
.autocart-filters label{
font-family: 'Circular Std', sans-serif;
margin: auto 0;
}
@font-face {
font-family: 'Circular Std';
src: url('fonts/CircularStd-Medium.woff2') format('woff2'),
url('fonts/CircularStd-Medium.woff') format('woff');
font-weight: 400;
font-style: normal;
font-display: swap;
}
.mdc-chip{
box-shadow: 0 1px 4px rgba(0,0,0,.4)!important;
border: 2px solid var(--mdc-theme-primary) !important;
background-color: #ffffff;
}
#tabs-viewer{
/* height: fit-content; */
position: sticky;
max-height: 75vh;
overflow: auto;
}
.overflow-x-scroll {
scroll-snap-type: x mandatory;
}
.flex-shrink-0 {
scroll-snap-align: start;
}
.sticky-container{
position: sticky;
}

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.

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.

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.

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.

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.

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.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,13 @@
<html><head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx</center>
</body></html><!-- a padding to disable MSIE and Chrome friendly error page --><!-- a padding to disable MSIE and Chrome friendly error page --><!-- a padding to disable MSIE and Chrome friendly error page --><!-- a padding to disable MSIE and Chrome friendly error page --><!-- a padding to disable MSIE and Chrome friendly error page --><!-- a padding to disable MSIE and Chrome friendly error page -->

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.

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="16px" height="16px" viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{fill:#BBBBBB;}
</style>
<g>
<polygon class="st0" points="0.9,5.5 3.1,3.4 8,8.3 12.9,3.4 15.1,5.5 8,12.6 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 623 B

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="16px" height="16px" viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;stroke:#FFFFFF;stroke-width:2;stroke-miterlimit:10;}
</style>
<polyline class="st0" points="4,7 7,10 12,5 "/>
</svg>

After

Width:  |  Height:  |  Size: 628 B

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="16px" height="16px" viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{fill:#8F83B8;}
</style>
<g>
<rect x="10" y="3" class="st0" width="6" height="2"/>
<polygon class="st0" points="3,7 9,7 9,1 3,1 3,3 0,3 0,5 3,5 "/>
<rect y="11" class="st0" width="6" height="2"/>
<polygon class="st0" points="13,9 7,9 7,15 13,15 13,13 16,13 16,11 13,11 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 791 B

133
autocart_assets/index.html Normal file
View File

@@ -0,0 +1,133 @@
<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href='http://fonts.googleapis.com/css?family=Open+Sans:400,300,600,700' rel='stylesheet' type='text/css'>
<link rel="stylesheet" href="css/reset.css"> <!-- CSS reset -->
<link rel="stylesheet" href="css/style.css"> <!-- Resource style -->
<script src="js/modernizr.js"></script> <!-- Modernizr -->
<title>Content Filters | CodyHouse</title>
</head>
<body>
<header class="cd-header">
<h1>Content Filters</h1>
</header>
<main class="cd-main-content">
<div class="cd-tab-filter-wrapper">
<div class="cd-tab-filter">
<ul class="cd-filters">
<li class="placeholder">
<a data-type="all" href="#0">All</a> <!-- selected option on mobile -->
</li>
<li class="filter"><a class="selected" href="#0" data-type="all">All</a></li>
<li class="filter" data-filter=".color-1"><a href="#0" data-type="color-1">Color 1</a></li>
<li class="filter" data-filter=".color-2"><a href="#0" data-type="color-2">Color 2</a></li>
</ul> <!-- cd-filters -->
</div> <!-- cd-tab-filter -->
</div> <!-- cd-tab-filter-wrapper -->
<section class="cd-gallery">
<ul>
<li class="mix color-1 check1 radio2 option3"><img src="img/img-1.jpg" alt="Image 1"></li>
<li class="mix color-2 check2 radio2 option2"><img src="img/img-2.jpg" alt="Image 2"></li>
<li class="mix color-1 check3 radio3 option1"><img src="img/img-3.jpg" alt="Image 3"></li>
<li class="mix color-1 check3 radio2 option4"><img src="img/img-4.jpg" alt="Image 4"></li>
<li class="mix color-1 check1 radio3 option2"><img src="img/img-5.jpg" alt="Image 5"></li>
<li class="mix color-2 check2 radio3 option3"><img src="img/img-6.jpg" alt="Image 6"></li>
<li class="mix color-2 check2 radio2 option1"><img src="img/img-7.jpg" alt="Image 7"></li>
<li class="mix color-1 check1 radio3 option4"><img src="img/img-8.jpg" alt="Image 8"></li>
<li class="mix color-2 check1 radio2 option3"><img src="img/img-9.jpg" alt="Image 9"></li>
<li class="mix color-1 check3 radio2 option4"><img src="img/img-10.jpg" alt="Image 10"></li>
<li class="mix color-1 check3 radio3 option2"><img src="img/img-11.jpg" alt="Image 11"></li>
<li class="mix color-2 check1 radio3 option1"><img src="img/img-12.jpg" alt="Image 12"></li>
<li class="gap"></li>
<li class="gap"></li>
<li class="gap"></li>
</ul>
<div class="cd-fail-message">No results found</div>
</section> <!-- cd-gallery -->
<div class="cd-filter">
<form>
<div class="cd-filter-block">
<h4>Search</h4>
<div class="cd-filter-content">
<input type="search" placeholder="Try color-1...">
</div> <!-- cd-filter-content -->
</div> <!-- cd-filter-block -->
<div class="cd-filter-block">
<h4>Check boxes</h4>
<ul class="cd-filter-content cd-filters list">
<li>
<input class="filter" data-filter=".check1" type="checkbox" id="checkbox1">
<label class="checkbox-label" for="checkbox1">Option 1</label>
</li>
<li>
<input class="filter" data-filter=".check2" type="checkbox" id="checkbox2">
<label class="checkbox-label" for="checkbox2">Option 2</label>
</li>
<li>
<input class="filter" data-filter=".check3" type="checkbox" id="checkbox3">
<label class="checkbox-label" for="checkbox3">Option 3</label>
</li>
</ul> <!-- cd-filter-content -->
</div> <!-- cd-filter-block -->
<div class="cd-filter-block">
<h4>Select</h4>
<div class="cd-filter-content">
<div class="cd-select cd-filters">
<select class="filter" name="selectThis" id="selectThis">
<option value="">Choose an option</option>
<option value=".option1">Option 1</option>
<option value=".option2">Option 2</option>
<option value=".option3">Option 3</option>
<option value=".option4">Option 4</option>
</select>
</div> <!-- cd-select -->
</div> <!-- cd-filter-content -->
</div> <!-- cd-filter-block -->
<div class="cd-filter-block">
<h4>Radio buttons</h4>
<ul class="cd-filter-content cd-filters list">
<li>
<input class="filter" data-filter="" type="radio" name="radioButton" id="radio1" checked>
<label class="radio-label" for="radio1">All</label>
</li>
<li>
<input class="filter" data-filter=".radio2" type="radio" name="radioButton" id="radio2">
<label class="radio-label" for="radio2">Choice 2</label>
</li>
<li>
<input class="filter" data-filter=".radio3" type="radio" name="radioButton" id="radio3">
<label class="radio-label" for="radio3">Choice 3</label>
</li>
</ul> <!-- cd-filter-content -->
</div> <!-- cd-filter-block -->
</form>
<a href="#0" class="cd-close">Close</a>
</div> <!-- cd-filter -->
<a href="#0" class="cd-filter-trigger">Filters</a>
</main> <!-- cd-main-content -->
<script src="js/jquery-2.1.1.js"></script>
<script src="js/jquery.mixitup.min.js"></script>
<script src="js/main.js"></script> <!-- Resource jQuery -->
</body>
</html>

View File

@@ -0,0 +1,62 @@
class AppliedFilters {
constructor(dataStore, filterManager) {
this.dataStore = dataStore;
this.filterManager = filterManager;
this.container = document.querySelector('#applied-filters-block');
this.render();
this.dataStore.subscribe(() => this.render());
}
removeFilter(type, value) {
this.dataStore.removeFilter(type, value);
this.filterManager.removeFilter(type, value);
}
render() {
const appliedFilters = this.dataStore.getAppliedFilters();
const chipSetContainer = document.createElement('div');
chipSetContainer.classList.add('mdc-chip-set');
appliedFilters.forEach((filter) => {
filter.values.forEach((fvalue) => {
const id = `${filter.type}-${fvalue}`.replace(/[^a-zA-Z0-9-_]/g, '_');
const chip = document.createElement('div');
chip.innerHTML = `
<div id="chip-${id}" class="mdc-chip">
<div class="mdc-chip__ripple"></div>
<span role="gridcell">
<span role="button" tabindex="0" class="mdc-chip__primary-action">
<span class="mdc-chip__text">
<b>${filter.type.replace(/_/g, " ").replace(/\b\w/g, l => l.toUpperCase())}</b>: ${fvalue}
</span>
</span>
</span>
<span role="gridcell">
<svg class="mdc-chip__icon mdc-chip__icon--trailing filter-tags" id="close-${id}"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
aria-hidden="true" filter-tags>
<path stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12">
</path>
</svg>
</span>
</div>`;
chip.querySelector(`#close-${id}`).addEventListener('click', () => this.removeFilter(filter.type, fvalue));
chipSetContainer.appendChild(chip);
});
});
// Clear existing content before appending new elements
this.container.innerHTML = '';
this.container.appendChild(chipSetContainer);
// Initialize MDC chips
const chipSet = new mdc.chips.MDCChipSet(chipSetContainer);
}
}

View File

@@ -0,0 +1,188 @@
class AppointmentForm {
constructor(vehicle) {
this.vehicle = vehicle;
this.initForm();
this.modalDiv = null;
this.name = 'appointmentForm';
}
initForm() {
this.modalDiv = document.createElement('div');
const inputIds = [
{
id: 'agreeToOffersCheckbox',
name: 'agreeToOffersCheckbox',
text: 'I agree to receive periodical offers, newsletter, safety and recall updates from the dealership. Consent can be withdrawn at any time.',
},
{
id: 'privacyPolicyCheckbox',
name: 'privacyPolicyCheckbox',
text: 'By submitting this information, you are accepting that it may be collected, used and disclosed as described in our privacy policy.',
},
];
let checkboxes = ``;
inputIds.forEach((input) => {
checkboxes += `
<div class=" mdc-form-field flex flex-row flex-nowrap w-full my-2 lg:my-0 ${input.name} hover:text-red-500">
<div class="mdc-checkbox mdc-theme-primary">
<input type="checkbox" class="mdc-checkbox__native-control ${input.name}" id="${input.id}" />
<div class="mdc-checkbox__background rounded-md">
<svg class="mdc-checkbox__checkmark" viewBox="0 0 24 24">
<path class="mdc-checkbox__checkmark-path" fill="none" d="M1.73,12.91 8.1,19.28 22.79,4.59"/>
</svg>
<div class="mdc-checkbox__mixedmark"></div>
</div>
<div class="mdc-checkbox__ripple p-2"></div>
</div>
<label for="${input.id}" class="text-sm font-medium text-gray-900 dark:text-gray-300 transition duration-400"> ${input.text}</label>
</div>`;
});
this.modalDiv.innerHTML = `
<form class="p-4 md:p-5">
<div class="grid gap-4 grid-cols-2">
<div class="col-span-2">
<label for="name" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Name</label>
<input type="text" name="name" id="name" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" placeholder="Jane Doe" required="">
</div>
<div class="col-span-2">
<label for="email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Email</label>
<input type="email" name="email" id="email" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" placeholder="jane@gmail.com" required="">
</div>
<div class="col-span-2 ">
<label for="phone" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Phone</label>
<input type="tel" name="phone" id="phone" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" placeholder="(123) 456-7890" required="">
</div>
<div class="col-span-2 ">
<label for="preferred-contact" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Preferred Contact Method</label>
<select id="preferred-contact" name="preferred-contact" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500">
<option selected="">---</option>
<option value="Call">Call</option>
<option value="Email">Email</option>
<option value="Text">Text</option>
</select>
</div>
<div class="col-span-2">
<label for="message" name="message" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Product Description</label>
<textarea id="message" rows="4" class="block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="I am interested in..."></textarea>
</div>
</div>
${checkboxes}
<button class="w-full bg-gray-500 hover:bg-gray-800 text-white p-2 rounded-md transition duration-300" id="send-button">Send</button>
</form>
`;
this.checkboxes = this.modalDiv.querySelectorAll('.mdc-checkbox');
if (this.checkboxes) {
this.checkboxes.forEach((checkbox, index) => {
// Initialize the MDCCheckbox with the actual checkbox element
const checkboxElement = checkbox.querySelector(
`.mdc-checkbox__native-control.${inputIds[index].name}`
);
const checkboxInstance = new mdc.checkbox.MDCCheckbox(checkbox);
// If the checkbox is found, initialize the form field as well
if (checkboxElement) {
const formFieldInstance = new mdc.formField.MDCFormField(
this.modalDiv.querySelector(
`.mdc-form-field.${inputIds[index].name}`
)
);
formFieldInstance.input = checkboxElement;
} else {
console.error('Checkbox element not found:', inputIds[index].id);
}
});
} else {
console.error('Checkboxes not found');
}
this.initializeCheckboxes(this.modalDiv, inputIds);
this.initializeFormEventListeners(this.modalDiv);
return this.modalDiv;
}
initializeFormEventListeners(modalDiv) {
const formInputs = [
'name',
'email',
'phone',
'preferred-contact',
'message',
'agreeToOffersCheckbox',
'privacyPolicyCheckbox',
];
formInputs.forEach((id) => {
const inputElement = modalDiv.querySelector(`#${id}`);
if (inputElement) {
inputElement.addEventListener('input', () => {
this.saveFormValues(); // Save form values whenever an input changes
});
}
});
}
initializeFormInputs() {
const storedValues =
JSON.parse(localStorage.getItem(`autocart_${this.name}`)) || {};
for (const key in storedValues) {
const inputElement = document.getElementById(key);
if (inputElement) {
inputElement.value = storedValues[key];
inputElement.dispatchEvent(new Event('input')); // Dispatch input event
}
}
}
initializeCheckboxes(modalDiv, inputIds) {
this.checkboxes = modalDiv.querySelectorAll('.mdc-checkbox');
if (this.checkboxes) {
this.checkboxes.forEach((checkbox, index) => {
// Initialize the MDCCheckbox with the actual checkbox element
const checkboxElement = checkbox.querySelector(
`.mdc-checkbox__native-control.${inputIds[index].name}`
);
const checkboxInstance = new mdc.checkbox.MDCCheckbox(checkbox);
// If the checkbox is found, initialize the form field as well
if (checkboxElement) {
const formFieldInstance = new mdc.formField.MDCFormField(
modalDiv.querySelector(`.mdc-form-field.${inputIds[index].name}`)
);
formFieldInstance.input = checkboxElement;
} else {
console.error('Checkbox element not found:', inputIds[index].id);
}
});
} else {
console.error('Checkboxes not found');
}
}
saveFormValues() {
const formInputs = [
'name',
'email',
'phone',
'preferred-contact',
'message',
'agreeToOffersCheckbox',
'privacyPolicyCheckbox',
];
const formValues = {};
formInputs.forEach((input) => {
const inputElement = document.getElementById(input);
if (inputElement) {
formValues[input] = inputElement.value;
}
});
localStorage.setItem(`autocart_${this.name}`, JSON.stringify(formValues));
}
}

View File

@@ -0,0 +1,102 @@
class BodyTypeFilter {
constructor(bodyType, dataStore) {
this.value = bodyType.text;
this.count = bodyType.count;
this.dataStore = dataStore;
this.checkbox = null;
this.checked = bodyType.checked;
this.initCheckbox();
}
initCheckbox() {
const makeListBlock = document.createElement('div');
const dynamicId = `autocart_${this.value.toLowerCase().replace(/\s/g, '-').replace(/\./g, '')}`;
const dynamicClass = `autocart_term_${this.value.toLowerCase().replace(/\s/g, '-').replace(/\./g, '')}`;
makeListBlock.innerHTML = `
<li class="w-full rounded-t-lg flex items-center justify-between transition duration-400 autocart-filters">
<div class="flex items-center mdc-form-field flex-wrap justify-between items-start flex-row w-full ${dynamicClass} hover:text-red-500">
<label for="${dynamicId}" class="text-sm font-medium text-gray-900 dark:text-gray-300 transition duration-400 font-normal text-base">${this.value}</label>
<span class="autocart_count text-gray-500 ml-2 transition duration-400 font-normal text-base">
(${this.count})
</span>
</div>
<div class="mdc-checkbox mdc-theme-primary">
<input type="checkbox" class="mdc-checkbox__native-control ${dynamicClass}" id="${dynamicId}" />
<div class="mdc-checkbox__background rounded-md">
<svg class="mdc-checkbox__checkmark" viewBox="0 0 24 24">
<path class="mdc-checkbox__checkmark-path" fill="none" d="M1.73,12.91 8.1,19.28 22.79,4.59"/>
</svg>
<div class="mdc-checkbox__mixedmark"></div>
</div>
<div class="mdc-checkbox__ripple p-2"></div>
</div>
</li>
`;
this.checkbox = makeListBlock.querySelector('.mdc-checkbox');
if (this.checkbox) {
// Initialize the MDCCheckbox with the actual checkbox element
const checkboxElement = this.checkbox.querySelector(`.mdc-checkbox__native-control.${dynamicClass}`);
this.checkbox = new mdc.checkbox.MDCCheckbox(this.checkbox);
this.formField = new mdc.formField.MDCFormField(makeListBlock.querySelector(`.mdc-form-field.${dynamicClass}`));
this.formField.input = checkboxElement;
this.checkbox.listen('change', () => this.handleCheckboxChange());
} else {
console.error('Checkbox element not found:', dynamicId);
}
this.markCheckboxIfValueFiltered()
return makeListBlock;
}
markCheckboxIfValueFiltered() {
let ifAlreadyFiltered = this.dataStore.getAppliedFilters().find(filter=>filter.type == "body_type");
if(ifAlreadyFiltered){
if(ifAlreadyFiltered.values.includes(this.value)){
this.checkbox.checked = true;
}
}
}
handleCheckboxChange() {
shouldUpdateFilters = false;
const isChecked = this.checkbox.checked;
if (isChecked) {
this.dataStore.addFilter('body_type', this.value);
} else {
this.dataStore.removeFilter('body_type', this.value);
}
this.checked = isChecked;
createTransmissionList(this.dataStore.filterVehicles());
createExteriorList(this.dataStore.filterVehicles());
}
setParentFilter(value){
this.isParentSelected = value;
this.generateCardHTML();
}
uncheckCheckbox() {
if (this.checkbox) {
this.checkbox.checked = false;
this.handleCheckboxChange();
// this.checkbox.dispatchEvent(new Event('change'));
}
}
}

View File

@@ -0,0 +1,86 @@
class DataStore {
constructor(vehicles) {
this.vehicles = vehicles;
this.subscribers = [];
this.filters = {};
this.notifySubscribers();
}
subscribe(subscriber) {
this.subscribers.push(subscriber);
}
unsubscribe(subscriber) {
this.subscribers = this.subscribers.filter((s) => s !== subscriber);
}
unsubscribeAll() {
this.subscribers = [];
}
notifySubscribers(filteredVehicles = this.vehicles) {
const activeFilters = Object.keys(this.filters);
if (activeFilters.length > 0) {
filteredVehicles = this.filterVehicles(filteredVehicles);
}
this.subscribers.forEach((subscriber) => {
subscriber(filteredVehicles);
});
}
addFilter(type, value) {
this.filters[type] = this.filters[type] || [];
this.filters[type].push(value);
this.notifySubscribers();
}
removeFilter(type, value) {
if (this.filters[type]) {
this.filters[type] = this.filters[type].filter((filter) => filter !== value);
if (this.filters[type].length === 0) {
delete this.filters[type];
}
this.notifySubscribers();
}
}
filterVehicles(vehicles = this.vehicles) {
return vehicles.filter((vehicle) => {
for (const [type, values] of Object.entries(this.filters)) {
if(Array.isArray(vehicle[type])){
if(vehicle[type].some(value => values.includes(value))){
return true;
}
}
if (!values.includes(vehicle[type])) {
return false;
}
}
return true;
});
}
getAppliedFilters() {
return Object.entries(this.filters).map(([type, values]) => ({ type, values }));
}
sortBy(property, order) {
this.vehicles.sort((a, b) => {
const aValue = a[property];
const bValue = b[property];
if (order === 'asc') {
return aValue > bValue ? 1 : -1;
} else {
return bValue > aValue ? 1 : -1;
}
});
this.notifySubscribers();
}
}

View File

@@ -0,0 +1,50 @@
class DisplayMode {
constructor(containerId) {
this.containerId = containerId || '.display-products .row';
this.container = document.querySelector(this.containerId);
this.init();
}
init() {
const gridButton = document.getElementById('grid-view');
const listButton = document.getElementById('list-view');
gridButton.addEventListener('click', () => this.toggleView('grid'));
listButton.addEventListener('click', () => this.toggleView('list'));
}
toggleView(viewMode) {
const rowElement = document.querySelector('.vehicles-container');
// Remove 'selected' class from both buttons
document.getElementById('grid-view').classList.remove('selected');
document.getElementById('list-view').classList.remove('selected');
// Add the appropriate classes based on the clicked button
if (viewMode === 'grid') {
rowElement.classList.remove('flex', 'flex-col', 'items-stretch');
rowElement.classList.add('grid', 'md:grid-cols-2', 'lg:grid-cols-3', 'transition-all', 'duration-500');
document.getElementById('grid-view').classList.add('selected');
} else if (viewMode === 'list') {
rowElement.classList.remove('grid', 'md:grid-cols-2', 'lg:grid-cols-3');
rowElement.classList.add('flex', 'flex-col', 'items-stretch', 'transition-all', 'duration-500');
document.getElementById('list-view').classList.add('selected');
}
// Update individual cards based on viewMode
const cards = rowElement.querySelectorAll('.vehicle-card');
cards.forEach(card => {
const vehicleImage = card.querySelector('.vehicle-image');
vehicleImage.classList.remove('w-1/4', 'w-full');
if (viewMode === 'list') {
card.classList.add('flex', 'flex-row' ,'justify-start', 'items-stretch', 'gap-3')
// flex flex-row justify-start gap-3
vehicleImage.classList.add('w-1/4');
} else {
card.classList.remove('flex', 'flex-row' ,'justify-start', 'items-stretch', 'gap-3')
vehicleImage.classList.add('w-full');
}
});
}
}

View File

@@ -0,0 +1,104 @@
class ExteriorFilter {
constructor(exterior, dataStore) {
this.value = exterior.text;
this.count = exterior.count;
this.dataStore = dataStore;
this.checkbox = null;
this.checked = exterior.checked;
this.initCheckbox();
}
initCheckbox() {
const makeListBlock = document.createElement('div');
const dynamicId = `autocart_${this.value.toLowerCase().replace(/\s/g, '-').replace(/\./g, '')}`;
const dynamicClass = `autocart_term_${this.value.toLowerCase().replace(/\s/g, '-').replace(/\./g, '')}`;
makeListBlock.innerHTML = `
<li class="w-full rounded-t-lg flex items-center justify-between transition duration-400 autocart-filters">
<div class="flex items-center mdc-form-field flex-wrap justify-between items-start flex-row w-full ${dynamicClass} hover:text-red-500">
<label for="${dynamicId}" class="text-sm font-medium text-gray-900 dark:text-gray-300 transition duration-400 font-normal text-base">${this.value}</label>
<span class="autocart_count text-gray-500 ml-2 transition duration-400 font-normal text-base">
(${this.count})
</span>
</div>
<div class="mdc-checkbox mdc-theme-primary">
<input type="checkbox" class="mdc-checkbox__native-control ${dynamicClass}" id="${dynamicId}" />
<div class="mdc-checkbox__background rounded-md">
<svg class="mdc-checkbox__checkmark" viewBox="0 0 24 24">
<path class="mdc-checkbox__checkmark-path" fill="none" d="M1.73,12.91 8.1,19.28 22.79,4.59"/>
</svg>
<div class="mdc-checkbox__mixedmark"></div>
</div>
<div class="mdc-checkbox__ripple p-2"></div>
</div>
</li>
`;
this.checkbox = makeListBlock.querySelector('.mdc-checkbox');
if (this.checkbox) {
// Initialize the MDCCheckbox with the actual checkbox element
const checkboxElement = this.checkbox.querySelector(`.mdc-checkbox__native-control.${dynamicClass}`);
this.checkbox = new mdc.checkbox.MDCCheckbox(this.checkbox);
this.formField = new mdc.formField.MDCFormField(makeListBlock.querySelector(`.mdc-form-field.${dynamicClass}`));
this.formField.input = checkboxElement;
this.checkbox.listen('change', () => this.handleCheckboxChange());
} else {
console.error('Checkbox element not found:', dynamicId);
}
this.markCheckboxIfValueFiltered()
return makeListBlock;
}
markCheckboxIfValueFiltered() {
let ifAlreadyFiltered = this.dataStore.getAppliedFilters().find(filter=>filter.type == "exterior_color");
if(ifAlreadyFiltered){
if(ifAlreadyFiltered.values.includes(this.value)){
this.checkbox.checked = true;
}
}
}
handleCheckboxChange() {
shouldUpdateFilters = false;
const isChecked = this.checkbox.checked;
if (isChecked) {
this.dataStore.addFilter('exterior_color', this.value);
} else {
this.dataStore.removeFilter('exterior_color', this.value);
}
this.checked = isChecked;
}
setParentFilter(value){
this.isParentSelected = value;
this.generateCardHTML();
}
uncheckCheckbox() {
if (this.checkbox) {
this.checkbox.checked = false;
// this.checkbox.dispatchEvent(new Event('change'));
this.handleCheckboxChange();
}
}
}

View File

@@ -0,0 +1,35 @@
class Filter {
constructor(type, value, dataStore, children = []) {
this.type = type;
this.value = value;
this.dataStore = dataStore;
this.children = children;
this.checkbox = null;
this.initCheckbox();
}
initCheckbox() {
// Initialize the checkbox and handle change events
this.checkbox.addEventListener('change', () => this.handleCheckboxChange());
}
handleCheckboxChange() {
if (this.checkbox.checked) {
this.dataStore.addFilter(this);
} else {
this.dataStore.removeFilter(this);
}
// Update UI or perform other actions based on the change
}
generateCardHTML() {
// Default implementation for generating HTML
return `
<li class="filter-item">
<span>${this.type}: ${this.value}</span>
<!-- Other HTML elements for the filter -->
</li>
`;
}
}

View File

@@ -0,0 +1,152 @@
class FilterManager {
constructor(dataStore) {
this.dataStore = dataStore;
this.makeFilters = [];
this.modelFilters = [];
this.trimFilters = [];
this.bodyTypeFilters = [];
this.transmissionFilters = [];
this.exteriorFilters = [];
this.subscribers = [];
}
createMakeFilter(make) {
const makeFilter = new MakeFilter(make, this.dataStore);
this.makeFilters.push(makeFilter);
this.notifySubscribers();
return makeFilter;
}
createModelFilter(model) {
const modelFilter = new ModelFilter(model, this.dataStore);
this.modelFilters.push(modelFilter);
this.notifySubscribers();
return modelFilter;
}
createTrimFilter(trim) {
const trimFilter = new TrimFilter(trim, this.dataStore);
this.trimFilters.push(trimFilter);
this.notifySubscribers();
return trimFilter;
}
createTransmissionFilter(transmission) {
const transmissionFilter = new TransmissionFilter(transmission, this.dataStore);
this.transmissionFilters.push(transmissionFilter);
this.notifySubscribers();
return transmissionFilter;
}
createExteriorFilter(exterior) {
const exteriorFilter = new ExteriorFilter(exterior, this.dataStore);
this.exteriorFilters.push(exteriorFilter);
this.notifySubscribers();
return exteriorFilter;
}
createBodyTypeFilter(bodyType) {
const bodyTypeFilter = new BodyTypeFilter(bodyType, this.dataStore);
this.bodyTypeFilters.push(bodyTypeFilter);
this.notifySubscribers();
return bodyTypeFilter;
}
removeFilter(filterType, filterValue) {
this.dataStore.removeFilter(filterType, filterValue);
if (filterType === 'make') {
const makeFilter = this.makeFilters.find(filter => filter.value === filterValue);
if (makeFilter) {
makeFilter.uncheckCheckbox();
}
}
if (filterType === 'model') {
const modelFilter = this.modelFilters.find(filter => filter.value === filterValue);
if (modelFilter) {
modelFilter.uncheckCheckbox();
}
}
if (filterType === 'trim') {
// because it came from response as an array
this.trimFilters.filter(filter => filter.value === filterValue).forEach(trimFilter=>{
if (trimFilter) {
trimFilter.uncheckCheckbox();
}
});
}
if (filterType === 'body_type') {
const bodyType = this.bodyTypeFilters.find(filter => filter.value === filterValue);
if (bodyType) {
bodyType.uncheckCheckbox();
}
}
if (filterType === 'transmission_type') {
const transmission = this.transmissionFilters.find(filter => filter.value === filterValue);
if (transmission) {
transmission.uncheckCheckbox();
}
}
if (filterType === 'exterior_color') {
const exterior = this.exteriorFilters.find(filter => filter.value === filterValue);
if (exterior) {
exterior.uncheckCheckbox();
}
}
this.notifySubscribers();
}
resetList(type) {
if (type === 'make') {
this.makeFilters = [];
}
if (type === 'model') {
this.modelFilters = [];
}
if (type === 'trim') {
this.trimFilters = [];
}
if (type === 'body_type') {
this.bodyTypeFilters = [];
}
if (type === 'transmission_type') {
this.transmissionFilters = [];
}
if (type === 'exterior_color') {
this.exteriorFilters = [];
}
this.notifySubscribers();
}
notifySubscribers() {
this.subscribers.forEach(subscriber => subscriber({
makeFilters: this.makeFilters,
modelFilters: this.modelFilters,
trimFilters: this.trimFilters
}));
}
subscribe(subscriber) {
this.subscribers.push(subscriber);
}
unsubscribe(subscriber) {
this.subscribers = this.subscribers.filter(sub => sub !== subscriber);
}
}

View File

@@ -0,0 +1,498 @@
class FinanceForm {
constructor(vehicle, notifyParentCallback) {
this.notifyParentCallback = notifyParentCallback;
this.modalDiv = null;
this.name = 'financeForm';
this.frequencyMap = {
12: 'Monthly',
26: 'Bi-Weekly',
52: 'Weekly',
};
this.vehicle = vehicle;
// Default values
this.loanTerm = 12;
this.intRate = 7.99;
this.downPayment = 0;
this.tradeValue = 0;
this.paymentFrequency = 26;
// Check if values exist in localStorage, and if so, override default values
const storedValues =
JSON.parse(localStorage.getItem(`autocart_${this.name}`)) || {};
if (storedValues) {
this.loanTerm = parseInt(storedValues.loanTerm) || this.loanTerm;
this.intRate = parseFloat(storedValues.customRateValue) || this.intRate;
this.downPayment = parseInt(storedValues.downPayment) || this.downPayment;
this.tradeValue = parseInt(storedValues.tradeValue) || this.tradeValue;
this.paymentFrequency =
parseInt(storedValues.paymentFrequency) || this.paymentFrequency;
}
// Calculate other values based on the provided vehicle and overridden inputs
let amount = parseFloat(this.vehicle.advertise_price) || 0;
let months = parseFloat(this.loanTerm) || 0;
let down = parseFloat(this.downPayment) || 0;
let trade = parseFloat(this.tradeValue) || 0;
let totalDown = down + trade;
let annInterest = parseFloat(this.intRate) || 0;
let monInt = annInterest / 1200;
let financeTotal = amount - totalDown;
let numberOfPayments = months / (12 / this.paymentFrequency);
// Fix calculation for total cost of credit
let interest = financeTotal * monInt * numberOfPayments;
this.totalCostOfCredit = interest.toFixed(2);
this.totalObligation = (
financeTotal + parseFloat(this.totalCostOfCredit)
).toFixed(2);
this.payment =
(
(monInt + monInt / (Math.pow(1 + monInt, months) - 1)) *
(amount - (totalDown || 0))
).toFixed(2) /
(this.paymentFrequency / 12);
// Notify parent callback with the updated values
this.notifyParentCallback(this);
// Initialize the form
this.initForm();
// this.initializeFormInputs();
}
initForm() {
this.modalDiv = document.createElement('div');
// const modalDiv = document.createElement('div');
this.modalDiv.innerHTML = `
<div class="bg-white rounded-md grid lg:grid-cols-6 gap-4">
<div class="lg:col-span-4">
<!-- Tab Navigation -->
<ul id="tabList" class="grid lg:grid-cols-2 text-sm font-medium text-center text-gray-500 dark:text-gray-400 mb-6 w-full list-none">
<li class="flex-1">
<a href="#" class="tab-link inline-block px-4 py-3 rounded-lg hover:text-gray-900 hover:bg-gray-100 w-full active bg-gray-600 text-white" data-tab="tab1">Personalize Your Options</a>
</li>
<li class="flex-1">
<a href="#" class="tab-link inline-block px-4 py-3 rounded-lg hover:text-gray-900 hover:bg-gray-100 dark:hover:bg-gray-800 dark:hover:text-white w-full" data-tab="tab2">Apply For Financing</a>
</li>
</ul>
<div id="tab-viewer" class="gap-6">
</div>
<div class="tab-content" data-content="tab1">
<h4 class="text-xl mb-2">Lets Structure a Deal That Works For You</h4>
<p class="mb-4">
We understand that purchasing a vehicle is a big decision, so we want you to be paying a price that youre comfortable with.
<strong>Lets structure a deal that works for you below:</strong>
</p>
<span class="inline-block p-4 bg-gray-200 border rounded-t-lg">
<h5 class="text-lg font-bold">Finance Option</h5>
<p class="mb-2">
<strong class="paymentSpan">${this.payment}</strong> / <span class="paymentFrequencySpan">${this.frequencyMap[this.paymentFrequency]}</span>
</p>
<p class="text-sm"><span class="rateSpan">${this.intRate}</span>% APR for <span class="loanTermSpan">${this.loanTerm}</span> Months</p>
</span>
<form class="p-4 md:p-5 bg-gray-200 rounded-r-lg rounded-b-lg">
<div class="grid gap-4 mb-4 lg:grid-cols-2">
<div>
<label for="downPayment" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Down Payment</label>
<div class="flex">
<span class="inline-flex items-center px-3 text-sm text-gray-900 bg-gray-200 border rounded-e-0 border-gray-300 rounded-s-md dark:bg-gray-600 dark:text-gray-400 dark:border-gray-600 rounded-l-lg">
<i class="fas fa-dollar-sign"></i>
</span>
<input type="number" value="0" id="downPayment" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-r-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500">
</div>
</div>
<div>
<label for="tradeValue" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Vehicle Trade-in</label>
<div class="flex">
<span class="inline-flex items-center px-3 text-sm text-gray-900 bg-gray-200 border rounded-e-0 border-gray-300 rounded-s-md dark:bg-gray-600 dark:text-gray-400 dark:border-gray-600 rounded-l-lg">
<i class="fas fa-dollar-sign"></i>
</span>
<input type="number" id="tradeValue" value="0" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-r-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500">
</div>
</div>
<div>
<label for="loanTerm" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Loan Term</label>
<select name="loanTerm" id="loanTerm" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" required="">
<option value="12" selected>12 Month Term</option>
<option value="24">24 Month Term</option>
<option value="36">36 Month Term</option>
<option value="48">48 Month Term</option>
<option value="60">60 Month Term</option>
<option value="72">72 Month Term</option>
<option value="84">84 Month Term</option>
</select>
</div>
<div>
<label for="paymentFrequency" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Payment Frequency</label>
<select id="paymentFrequency" name="paymentFrequency" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500">
<option value="12">Monthly</option>
<option value="26" selected>Bi-Weekly</option>
<option value="52">Weekly</option>
</select>
</div>
<div class="">
<label for="rate" name="rate" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Program Rates (APR)</label>
<select id="rate" name="rate" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500 xl:w-96">
<option value="default-rate">Financing [Dealer Rate] (7.99%)</option>
<option value="custom-rate">Set Custom Rate</option>
</select>
</div>
<div class="text-gray-500 ml-2 text-3xl p-5" id="default-rate">
7.99%
</div>
<div class="hidden" id="custom-rate">
<label for="customRateValue" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Custom Rate</label>
<div class="flex">
<span class="inline-flex items-center px-3 text-sm text-gray-900 bg-gray-200 border rounded-e-0 border-gray-300 rounded-s-md dark:bg-gray-600 dark:text-gray-400 dark:border-gray-600 rounded-l-lg">
<i class="fas fa-percentage"></i>
</span>
<input type="number" id="customRateValue" value="5.99" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-r-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500">
</div>
</div>
</div>
</form>
</div>
<div class="tab-content hidden" data-content="tab2">
<h4 class="text-xl mb-2">Lets Structure a Deal That Works For You</h4>
<p class="mb-4">
We understand that purchasing a vehicle is a big decision, so we want you to be paying a price that youre comfortable with.
<strong>Lets structure a deal that works for you below:</strong>
</p>
<div id="appointmentFormContainer">
</div>
</div>
</div>
<div class="lg:col-span-2 p-6 bg-gray-200 rounded-md">
<div class="col ">
<div class="mb-4">
<h4 class="text-lg font-bold">Vehicle Price</h4>
<ul class="transmission-container list-none text-gray-900 w-48 text-sm font-medium rounded-lg w-full">
<li class="w-full flex items-center flex-wrap justify-between items-start flex-row w-full rounded-t-lg flex items-center justify-between transition duration-400 autocart-filters">
<span class="text-sm font-medium text-gray-900 dark:text-gray-300 transition duration-400 font-normal text-base">Vehicle Price</span>
<span class="autocart_count text-gray-500 ml-2 transition duration-400 font-normal text-base">
${this.vehicle.advertise_price.toLocaleString('en-CA', { style: 'currency', currency: 'CAD', minimumFractionDigits: 0, maximumFractionDigits: 0 })}
</span>
</li>
</ul>
</div>
<hr class="my-4 border-t border-gray-300">
<div class="mb-4">
<ul class="transmission-container list-none text-gray-900 w-48 text-sm font-medium rounded-lg w-full">
<li class="w-full flex items-center flex-wrap justify-between items-start flex-row w-full rounded-t-lg flex items-center justify-between transition duration-400 autocart-filters">
<span class="text-sm font-medium text-gray-900 dark:text-gray-300 transition duration-400 font-normal text-base">Finance Total</span>
<span class="autocart_count text-gray-500 ml-2 transition duration-400 font-normal text-base">
${this.vehicle.advertise_price.toLocaleString('en-CA', { style: 'currency', currency: 'CAD', minimumFractionDigits: 0, maximumFractionDigits: 0 })}
</span>
</li>
</ul>
</div>
<div class="mb-4">
<h4 class="text-lg font-bold">Finance Payment</h4>
<ul class="transmission-container list-none text-gray-900 w-48 text-sm font-medium rounded-lg w-full">
<li class="w-full flex items-center flex-wrap justify-between items-start flex-row w-full rounded-t-lg flex items-center justify-between transition duration-400 autocart-filters">
<span class="text-sm font-medium text-gray-900 dark:text-gray-300 transition duration-400 font-normal text-base">Loan Term</span>
<span class="autocart_count text-gray-500 ml-2 transition duration-400 font-normal text-base ">
<span class="loanTermSpan">${this.loanTerm} </span> Months
</span>
</li>
<li class="w-full flex items-center flex-wrap justify-between items-start flex-row w-full rounded-t-lg flex items-center justify-between transition duration-400 autocart-filters">
<span class="text-sm font-medium text-gray-900 dark:text-gray-300 transition duration-400 font-normal text-base">Program Rate</span>
<span class="autocart_count text-gray-500 ml-2 transition duration-400 font-normal text-base">
<span class="rateSpan">7.99</span>%
</span>
</li>
<li class="w-full flex items-center flex-wrap justify-between items-start flex-row w-full rounded-t-lg flex items-center justify-between transition duration-400 autocart-filters">
<span class="text-sm font-medium text-gray-900 dark:text-gray-300 transition duration-400 font-normal text-base">Frequency</span>
<span class="autocart_count text-gray-500 ml-2 transition duration-400 font-normal text-base paymentFrequencySpan">
${this.frequencyMap[this.paymentFrequency]}
</span>
</li>
</ul>
</div>
<div class="mb-4">
<h4 class="text-lg font-bold">Total</h4>
<ul class="transmission-container list-none text-gray-900 w-48 text-sm font-medium rounded-lg w-full">
<li class="w-full flex items-center flex-wrap justify-between items-start flex-row w-full rounded-t-lg flex items-center justify-between transition duration-400 autocart-filters">
<span class="text-sm font-medium text-gray-900 dark:text-gray-300 transition duration-400 font-normal text-base">Total Cost of Credit</span>
<span class="autocart_count text-gray-500 ml-2 tran∂sition duration-400 font-normal text-base totalCostOfCreditSpan">
${this.totalCostOfCredit.toLocaleString('en-CA', { style: 'currency', currency: 'CAD', minimumFractionDigits: 0, maximumFractionDigits: 0 })}
</span>
</li>
<li class="w-full flex items-center flex-wrap justify-between items-start flex-row w-full rounded-t-lg flex items-center justify-between transition duration-400 autocart-filters">
<span class="text-sm font-medium text-gray-900 dark:text-gray-300 transition duration-400 font-normal text-base">Total Obligation</span>
<span class="autocart_count text-gray-500 ml-2 transition duration-400 font-normal text-base totalObligationSpan">
${this.totalObligation.toLocaleString('en-CA', { style: 'currency', currency: 'CAD', minimumFractionDigits: 0, maximumFractionDigits: 0 })}
</span>
</li>
</ul>
</div>
</div>
<div class="mb-4 items-center flex flex-col">
<div class="py-2 inline-block transition duration-300">
<p>
<span class="paymentSpan">${(Number(this.payment) || 0).toLocaleString('en-CA', { style: 'currency', currency: 'CAD' })}</span>
/ <span class="paymentFrequencySpan">
${this.frequencyMap[this.paymentFrequency]}
</span>
</p>
</div>
<button class="w-full bg-gray-500 hover:bg-gray-800 text-white p-2 rounded-md transition duration-300" id="next-step">next step</button>
</div>
</div>
</div>
</div>
`;
const formContainer = this.modalDiv.querySelector(
'#appointmentFormContainer'
);
formContainer.appendChild(appointmentForm.initForm());
this.initializeTabNavigation(this.modalDiv);
this.initializeFormEventListeners(this.modalDiv);
return this.modalDiv;
}
// initializeFormInputs() {
// const storedValues = JSON.parse(localStorage.getItem(`autocart_${this.name}`)) || {};
// // const mergedValues = { ...this.defaultValues, ...storedValues };
// console.log(storedValues);
// for (const key in storedValues) {
// console.log(key);
// const inputElement = document.getElementById(key);
// if (inputElement) {
// inputElement.value = storedValues[key];
// }
// }
// this.updateView()
// }
initializeFormInputs() {
const storedValues =
JSON.parse(localStorage.getItem(`autocart_${this.name}`)) || {};
for (const key in storedValues) {
const inputElement = document.getElementById(key);
if (inputElement) {
inputElement.value = storedValues[key];
inputElement.dispatchEvent(new Event('input')); // Dispatch input event
}
}
this.updateView();
}
calculatePayments() {
var vehiclePrice = this.vehicle.advertise_price;
this.loanTerm = document.getElementById('loanTerm').value;
this.paymentFrequency = document.getElementById('paymentFrequency').value;
this.intRate =
document.getElementById('rate').value == 'default-rate'
? 7.99
: document.getElementById('customRateValue').value;
this.downPayment = document.getElementById('downPayment').value;
this.tradeValue = document.getElementById('tradeValue').value;
var amount = parseFloat(vehiclePrice) || 0,
months = parseFloat(this.loanTerm) || 0,
down = parseFloat(this.downPayment) || 0,
trade = parseFloat(this.tradeValue) || 0,
totalDown = down + trade,
annInterest = parseFloat(this.intRate) || 0,
monInt = annInterest / 1200;
var financeTotal = amount - totalDown;
var numberOfPayments = months / (12 / this.paymentFrequency);
var monthlyPayment =
(
(monInt + monInt / (Math.pow(1 + monInt, months) - 1)) *
(amount - (totalDown || 0))
).toFixed(2) /
(this.paymentFrequency / 12);
this.totalCostOfCredit = (
monthlyPayment * numberOfPayments -
financeTotal
).toFixed(2);
this.totalObligation = (
parseFloat(this.totalCostOfCredit) + parseFloat(financeTotal)
).toFixed(2);
this.payment =
(
(monInt + monInt / (Math.pow(1 + monInt, months) - 1)) *
(amount - (totalDown || 0))
).toFixed(2) /
(this.paymentFrequency / 12);
this.updateView();
}
updateView() {
['default-rate', 'custom-rate'].forEach((el) => {
document.getElementById(el).classList.add('hidden');
});
document
.getElementById(document.getElementById('rate').value)
.classList.remove('hidden');
if (document.querySelectorAll('.loanTermSpan').length) {
document.querySelectorAll('.loanTermSpan').forEach((el) => {
el.innerHTML = `${this.loanTerm}`;
});
}
if (document.querySelectorAll('.rateSpan').length) {
document.querySelectorAll('.rateSpan').forEach((el) => {
el.innerHTML = `${this.intRate}`;
});
}
if (document.querySelectorAll('.paymentSpan').length) {
document.querySelectorAll('.paymentSpan').forEach((el) => {
el.innerHTML = (Number(this.payment) || 0).toLocaleString('en-CA', {
style: 'currency',
currency: 'CAD',
});
});
}
if (document.querySelectorAll('.paymentFrequencySpan').length) {
document.querySelectorAll('.paymentFrequencySpan').forEach((el) => {
el.innerHTML = this.frequencyMap[this.paymentFrequency];
});
}
if (document.querySelectorAll('.totalCostOfCreditSpan').length) {
document.querySelectorAll('.totalCostOfCreditSpan').forEach((el) => {
el.innerHTML = (Number(this.totalCostOfCredit) || 0).toLocaleString(
'en-CA',
{ style: 'currency', currency: 'CAD' }
);
});
}
if (document.querySelectorAll('.totalObligationSpan').length) {
document.querySelectorAll('.totalObligationSpan').forEach((el) => {
el.innerHTML = (Number(this.totalObligation) || 0).toLocaleString(
'en-CA',
{ style: 'currency', currency: 'CAD' }
);
});
}
}
goToNextTab() {
let tab1 = document.querySelector('[data-tab="tab1"]');
let tab2 = document.querySelector('[data-tab="tab2"]');
let backButton = document.getElementById('next-step');
if (tab1 && tab2 && backButton) {
if (tab1.classList.contains('active')) {
// Switch to tab2 and update button text to "Back"
tab2.click();
backButton.innerText = 'Back';
} else if (tab2.classList.contains('active')) {
// Switch to tab1 and update button text to "Next step"
tab1.click();
backButton.innerText = 'Next step';
}
}
}
initializeTabNavigation(modalDiv) {
const tabLinks = modalDiv.querySelectorAll('.tab-link');
const tabContents = modalDiv.querySelectorAll('.tab-content');
let nextStepButton = modalDiv.querySelector('#next-step');
if (nextStepButton) {
nextStepButton.addEventListener('click', this.goToNextTab.bind(this));
}
tabLinks.forEach(function (tabLink) {
tabLink.addEventListener('click', function (event) {
event.preventDefault();
tabLinks.forEach(function (link) {
link.classList.remove('active', 'bg-gray-600', 'text-white');
});
tabContents.forEach(function (content) {
content.classList.add('hidden');
});
const targetTab = this.getAttribute('data-tab');
this.classList.add('active', 'bg-gray-600', 'text-white');
let backButtonTexts = { tab1: 'Next Step', tab2: 'Back To Options' };
nextStepButton.innerText = backButtonTexts[targetTab];
modalDiv
.querySelector(`.tab-content[data-content="${targetTab}"]`)
.classList.remove('hidden');
});
});
}
initializeFormEventListeners(modalDiv) {
const formInputs = [
'loanTerm',
'intRate',
'downPayment',
'tradeValue',
'paymentFrequency',
'rate',
'customRateValue',
];
formInputs.forEach((id) => {
const inputElement = modalDiv.querySelector(`#${id}`);
if (inputElement) {
inputElement.addEventListener('input', () => {
this.calculatePayments();
this.saveFormValues(); // Save form values whenever an input changes
});
}
});
}
saveFormValues() {
const formInputs = [
'loanTerm',
'intRate',
'downPayment',
'tradeValue',
'paymentFrequency',
'rate',
'customRateValue',
];
const formValues = {};
formInputs.forEach((input) => {
const inputElement = document.getElementById(input);
if (inputElement) {
formValues[input] = inputElement.value;
}
});
if (formValues['rate'] == 'default-rate') {
formValues['customRateValue'] = 7.99;
}
localStorage.setItem(`autocart_${this.name}`, JSON.stringify(formValues));
}
}

View File

@@ -0,0 +1,140 @@
class MakeFilter {
constructor(make, dataStore) {
this.value = make.text;
this.count = make.count;
this.dataStore = dataStore;
this.checkbox = null;
this.models = make.models.map(model=> filterManager.createModelFilter({ text: model.model, count: model.count, trims: model.trims, parent: this.value, checked: false }, this.dataStore));
this.checked = make.checked;
this.formField = null;
// this.initCheckbox();
this.subscribers = [];
this.models.forEach(m => {
m.subscribe(m=> initTrimContainer(m))
});
}
initCheckbox() {
const makeListBlock = document.createElement('div');
const dynamicId = `autocart_${this.value.toLowerCase().replace(/\s/g, '-').replace(/\./g, '')}`;
const dynamicClass = `autocart_term_${this.value.toLowerCase().replace(/\s/g, '-').replace(/\./g, '')}`;
makeListBlock.innerHTML = `
<li class="w-full rounded-t-lg flex items-center justify-between transition duration-400 autocart-filters">
<div class="flex items-center mdc-form-field flex-wrap justify-between items-start flex-row w-full ${dynamicClass} hover:text-red-500">
<label for="${dynamicId}" class="text-sm font-medium text-gray-900 dark:text-gray-300 transition duration-400 font-normal text-base">${this.value}</label>
<span class="autocart_count text-gray-500 ml-2 transition duration-400 font-normal text-base">
(${this.count})
</span>
</div>
<div class="mdc-checkbox mdc-theme-primary">
<input type="checkbox" class="mdc-checkbox__native-control ${dynamicClass}" id="${dynamicId}" />
<div class="mdc-checkbox__background rounded-md">
<svg class="mdc-checkbox__checkmark" viewBox="0 0 24 24">
<path class="mdc-checkbox__checkmark-path" fill="none" d="M1.73,12.91 8.1,19.28 22.79,4.59"/>
</svg>
<div class="mdc-checkbox__mixedmark"></div>
</div>
<div class="mdc-checkbox__ripple p-2"></div>
</div>
</li>`;
this.checkbox = makeListBlock.querySelector('.mdc-checkbox');
if (this.checkbox) {
const checkboxElement = this.checkbox.querySelector(`.mdc-checkbox__native-control.${dynamicClass}`);
this.checkbox = new mdc.checkbox.MDCCheckbox(this.checkbox);
this.formField = new mdc.formField.MDCFormField(makeListBlock.querySelector(`.mdc-form-field.${dynamicClass}`));
this.formField.input = checkboxElement;
this.checkbox.listen('change', () => this.handleCheckboxChange());
} else {
console.error('Checkbox element not found:', dynamicId);
}
this.markCheckboxIfValueFiltered()
return makeListBlock;
}
markCheckboxIfValueFiltered() {
if (this.checked) {
this.checkbox.checked = true;
} else {
let ifAlreadyFiltered = this.dataStore.getAppliedFilters().find(filter => filter.type == "make");
if (ifAlreadyFiltered) {
if (ifAlreadyFiltered.values.includes(this.value)) {
this.checkbox.checked = true;
}
}
}
// }
// let ifAlreadyFiltered = this.dataStore.getAppliedFilters().find(filter=>filter.type == "make");
// if(ifAlreadyFiltered){
// if(ifAlreadyFiltered.values.includes(this.value)){
// this.checkbox.checked = true;
// }
// }
}
handleCheckboxChange() {
shouldUpdateFilters = false;
const isChecked = this.checkbox.checked;
if (isChecked) {
this.dataStore.addFilter('make', this.value);
// this.updateModelFilters(this.value);
} else {
this.dataStore.removeFilter('make', this.value);
}
this.checked = isChecked;
this.models.forEach(model=>model.uncheckCheckbox())
createBodyTypeList(this.dataStore.filterVehicles());
createTransmissionList(this.dataStore.filterVehicles());
createExteriorList(this.dataStore.filterVehicles());
this.notifySubscribers();
}
generateCardHTML() {
if(this.checked){
// this.checkbox.checked = true;
setTimeout(() => {
shouldUpdateFilters = false;
this.dataStore.addFilter('make', this.value);
this.notifySubscribers();
}, 100);
}
return this.initCheckbox();
}
updateModelFilters(value) {
this.models.forEach(model => {
model.setParentFilter(value)
});
}
uncheckCheckbox() {
if (this.checkbox) {
this.checkbox.checked = false;
this.handleCheckboxChange();
// this.checkbox.dispatchEvent(new Event('change'));
}
}
notifySubscribers() {
this.subscribers.forEach(subscriber => subscriber(this));
}
subscribe(subscriber) {
this.subscribers.push(subscriber);
}
unsubscribe(subscriber) {
this.subscribers = this.subscribers.filter(sub => sub !== subscriber);
}
}

View File

@@ -0,0 +1,98 @@
class Modal {
constructor(vehicle) {
this.vehicle = vehicle;
this.modalDiv;
this.initModal();
}
toggleModal(html) {
if (this.modalDiv) {
const modalBody = this.modalDiv.querySelector('#ac-modal-body');
modalBody.innerHTML = '';
if (html) {
modalBody.appendChild(html.initForm());
}
}
const body = document.querySelector('body');
const modal = document.querySelector('.modal');
modal.classList.toggle('opacity-0');
modal.classList.toggle('pointer-events-none');
body.classList.toggle('modal-active');
}
initModal() {
// Create a new div element
const modalDiv = document.createElement('div');
modalDiv.classList.add(
'modal',
'z-50',
'opacity-0',
'pointer-events-none',
'fixed',
'w-full',
'h-full',
'top-0',
'left-0',
'flex',
'items-center',
'justify-center'
);
modalDiv.innerHTML = `
<div class="modal-overlay absolute w-full h-full bg-gray-900 opacity-50"></div>
<div class="modal-container bg-white w-11/12 md:w-3/4 h-3/4 lg:h-11/12 mx-auto rounded shadow-lg z-50 overflow-y-scroll relative no-scrollbar">
<div class="modal-close absolute top-0 right-0 cursor-pointer flex flex-col items-center mt-4 mr-4 text-white text-sm z-50">
<svg class="fill-current text-white" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
<path d="M14.53 4.53l-1.06-1.06L9 7.94 4.53 3.47 3.47 4.53 7.94 9l-4.47 4.47 1.06 1.06L9 10.06l4.47 4.47 1.06-1.06L10.06 9z"></path>
</svg>
<span class="text-sm">(Esc)</span>
</div>
<div class="modal-content py-4 text-left px-6">
<!--Title-->
<div class="flex justify-between items-center pb-3">
<p class="text-2xl font-bold">Private Vehicle Appointment Booking</p>
<div class="modal-close cursor-pointer z-50">
<svg class="fill-current text-black" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
<path d="M14.53 4.53l-1.06-1.06L9 7.94 4.53 3.47 3.47 4.53 7.94 9l-4.47 4.47 1.06 1.06L9 10.06l4.47 4.47 1.06-1.06L10.06 9z"></path>
</svg>
</div>
</div>
<!-- BODY -->
<div id="ac-modal-body"></div>
</div>
</div>
`;
const overlay = modalDiv.querySelector('.modal-overlay');
overlay.addEventListener('click', function () {
const activeModal = modalDiv.querySelector('.modal');
activeModal &&
activeModal.classList.contains('opacity-0') &&
this.toggleModal();
});
var closemodal = modalDiv.querySelectorAll('.modal-close');
for (var i = 0; i < closemodal.length; i++) {
closemodal[i].addEventListener('click', this.toggleModal);
}
document.onkeydown = (evt) => {
evt = evt || window.event;
var isEscape = false;
if ('key' in evt) {
isEscape = evt.key === 'Escape' || evt.key === 'Esc';
} else {
isEscape = evt.keyCode === 27;
}
if (isEscape && document.body.classList.contains('modal-active')) {
this.toggleModal();
}
};
this.modalDiv = modalDiv;
return modalDiv;
}
}
document.addEventListener('DOMContentLoaded', function () {
console.log('modal is here');
});

View File

@@ -0,0 +1,118 @@
class ModelFilter {
constructor(model, dataStore) {
this.value = model.text;
this.count = model.count;
this.parent = model.parent;
this.dataStore = dataStore;
this.checkbox = null;
this.checked = model.checked;
this.formField = null;
this.trims = model.trims.map(trim=> filterManager.createTrimFilter({ text: trim.text, count: trim.count, parent: this.value, checked: false }, this.dataStore));
this.subscribers = [];
this.initCheckbox();
}
initCheckbox() {
const makeListBlock = document.createElement('div');
const dynamicId = `autocart_${this.value.toLowerCase().replace(/\s/g, '-').replace(/\./g, '')}`;
const dynamicClass = `autocart_term_${this.value.toLowerCase().replace(/\s/g, '-').replace(/\./g, '')}`;
makeListBlock.innerHTML = `
<li class="w-full rounded-t-lg flex items-center justify-between transition duration-400 autocart-filters">
<div class="flex items-center mdc-form-field flex-wrap justify-between items-start flex-row w-full ${dynamicClass} hover:text-red-500">
<label for="${dynamicId}" class="text-sm font-medium text-gray-900 dark:text-gray-300 transition duration-400 font-normal text-base">${this.value}</label>
<span class="autocart_count text-gray-500 ml-2 transition duration-400 font-normal text-base">
(${this.count})
</span>
</div>
<div class="mdc-checkbox mdc-theme-primary">
<input type="checkbox" class="mdc-checkbox__native-control ${dynamicClass}" id="${dynamicId}" />
<div class="mdc-checkbox__background rounded-md">
<svg class="mdc-checkbox__checkmark" viewBox="0 0 24 24">
<path class="mdc-checkbox__checkmark-path" fill="none" d="M1.73,12.91 8.1,19.28 22.79,4.59"/>
</svg>
<div class="mdc-checkbox__mixedmark"></div>
</div>
<div class="mdc-checkbox__ripple p-2"></div>
</div>
</li>
`;
this.checkbox = makeListBlock.querySelector('.mdc-checkbox');
if (this.checkbox) {
// Initialize the MDCCheckbox with the actual checkbox element
const checkboxElement = this.checkbox.querySelector(`.mdc-checkbox__native-control.${dynamicClass}`);
this.checkbox = new mdc.checkbox.MDCCheckbox(this.checkbox);
this.formField = new mdc.formField.MDCFormField(makeListBlock.querySelector(`.mdc-form-field.${dynamicClass}`));
this.formField.input = checkboxElement;
this.checkbox.listen('change', () => this.handleCheckboxChange());
} else {
console.error('Checkbox element not found:', dynamicId);
}
this.markCheckboxIfValueFiltered()
return makeListBlock;
}
markCheckboxIfValueFiltered() {
let ifAlreadyFiltered = this.dataStore.getAppliedFilters().find(filter=>filter.type == "model");
if(ifAlreadyFiltered){
if(ifAlreadyFiltered.values.includes(this.value)){
this.checkbox.checked = true;
}
}
}
handleCheckboxChange() {
shouldUpdateFilters = false;
const isChecked = this.checkbox.checked;
if (isChecked) {
this.dataStore.addFilter('model', this.value);
} else {
this.dataStore.removeFilter('model', this.value);
// this.trims = [];
}
this.checked = isChecked;
createBodyTypeList(this.dataStore.filterVehicles());
createTransmissionList(this.dataStore.filterVehicles());
createExteriorList(this.dataStore.filterVehicles());
this.trims.forEach(trim=>trim.uncheckCheckbox())
this.notifySubscribers();
}
setParentFilter(value){
this.isParentSelected = value;
this.generateCardHTML();
}
uncheckCheckbox() {
if (this.checkbox) {
this.checkbox.checked = false;
this.handleCheckboxChange();
}
}
notifySubscribers() {
this.subscribers.forEach(subscriber => subscriber(this));
}
subscribe(subscriber) {
this.subscribers.push(subscriber);
}
unsubscribe(subscriber) {
this.subscribers = this.subscribers.filter(sub => sub !== subscriber);
}
}

View File

@@ -0,0 +1,59 @@
class SortBy {
constructor(dataStore) {
this.dataStore = dataStore;
this.sortByElement = null;
this.sortOptions = [
{ field: 'advertise_price', order: 'asc', label: 'Price: Low to High' },
{ field: 'advertise_price', order: 'desc', label: 'Price: High to Low' },
{ field: 'year', order: 'asc', label: 'Year: Low to High' },
{ field: 'year', order: 'desc', label: 'Year: High to Low' },
{ field: 'make', order: 'asc', label: 'Make Name: A to Z' },
{ field: 'make', order: 'desc', label: 'Make Name: Z to A' },
{ field: 'model', order: 'asc', label: 'Model Name: A to Z' },
{ field: 'model', order: 'desc', label: 'Model Name: Z to A' }
];
this.init();
}
init() {
// Get the ul element
const sortOptionsList = document.getElementById('sort-options-list');
// Dynamically create list items
this.sortOptions.forEach(option => {
const listItem = document.createElement('li');
listItem.className = 'mdc-list-item';
listItem.setAttribute('aria-selected', 'false');
listItem.setAttribute('data-value', JSON.stringify([option.field, option.order])); // Use field and order properties
const rippleSpan = document.createElement('span');
rippleSpan.className = 'mdc-list-item__ripple';
const textSpan = document.createElement('span');
textSpan.className = 'mdc-list-item__text';
textSpan.textContent = option.label; // Use label property
listItem.appendChild(rippleSpan);
listItem.appendChild(textSpan);
sortOptionsList.appendChild(listItem);
});
this.sortByElement = new mdc.select.MDCSelect(document.querySelector('#order-by'));
this.sortByElement.listen('MDCSelect:change', this.handleSortChange.bind(this));
}
handleSortChange(event) {
const selectedOption = this.sortByElement.value;
if (selectedOption) {
const [sortBy, order] = JSON.parse(selectedOption);
this.dataStore.sortBy(sortBy, order);
}
}
}

View File

@@ -0,0 +1,104 @@
class TransmissionFilter {
constructor(transmission, dataStore) {
this.value = transmission.text;
this.count = transmission.count;
this.parent = transmission.parent;
this.dataStore = dataStore;
this.checkbox = null;
this.checked = transmission.checked;
this.initCheckbox();
}
initCheckbox() {
const makeListBlock = document.createElement('div');
const dynamicId = `autocart_${this.value.toLowerCase().replace(/\s/g, '-').replace(/\./g, '')}`;
const dynamicClass = `autocart_term_${this.value.toLowerCase().replace(/\s/g, '-').replace(/\./g, '')}`;
makeListBlock.innerHTML = `
<li class="w-full rounded-t-lg flex items-center justify-between transition duration-400 autocart-filters">
<div class="flex items-center mdc-form-field flex-wrap justify-between items-start flex-row w-full ${dynamicClass} hover:text-red-500">
<label for="${dynamicId}" class="text-sm font-medium text-gray-900 dark:text-gray-300 transition duration-400 font-normal text-base">${this.value}</label>
<span class="autocart_count text-gray-500 ml-2 transition duration-400 font-normal text-base">
(${this.count})
</span>
</div>
<div class="mdc-checkbox mdc-theme-primary">
<input type="checkbox" class="mdc-checkbox__native-control ${dynamicClass}" id="${dynamicId}" />
<div class="mdc-checkbox__background rounded-md">
<svg class="mdc-checkbox__checkmark" viewBox="0 0 24 24">
<path class="mdc-checkbox__checkmark-path" fill="none" d="M1.73,12.91 8.1,19.28 22.79,4.59"/>
</svg>
<div class="mdc-checkbox__mixedmark"></div>
</div>
<div class="mdc-checkbox__ripple p-2"></div>
</div>
</li>
`;
this.checkbox = makeListBlock.querySelector('.mdc-checkbox');
if (this.checkbox) {
// Initialize the MDCCheckbox with the actual checkbox element
const checkboxElement = this.checkbox.querySelector(`.mdc-checkbox__native-control.${dynamicClass}`);
this.checkbox = new mdc.checkbox.MDCCheckbox(this.checkbox);
this.formField = new mdc.formField.MDCFormField(makeListBlock.querySelector(`.mdc-form-field.${dynamicClass}`));
this.formField.input = checkboxElement;
this.checkbox.listen('change', () => this.handleCheckboxChange());
} else {
console.error('Checkbox element not found:', dynamicId);
}
this.markCheckboxIfValueFiltered()
// console.log(dataStore.getAppliedFilters());
return makeListBlock;
}
markCheckboxIfValueFiltered() {
let ifAlreadyFiltered = this.dataStore.getAppliedFilters().find(filter=>filter.type == "transmission_type");
if(ifAlreadyFiltered){
if(ifAlreadyFiltered.values.includes(this.value)){
this.checkbox.checked = true;
}
}
}
handleCheckboxChange() {
shouldUpdateFilters = false;
const isChecked = this.checkbox.checked;
if (isChecked) {
this.dataStore.addFilter('transmission_type', this.value);
} else {
this.dataStore.removeFilter('transmission_type', this.value);
}
this.checked = isChecked;
createExteriorList(this.dataStore.filterVehicles());
}
setParentFilter(value){
this.isParentSelected = value;
this.generateCardHTML();
}
uncheckCheckbox() {
if (this.checkbox) {
this.checkbox.checked = false;
this.handleCheckboxChange();
// this.checkbox.dispatchEvent(new Event('change'));
}
}
}

View File

@@ -0,0 +1,102 @@
class TrimFilter {
constructor(trim, dataStore) {
this.value = trim.text;
this.count = trim.count;
this.parent = trim.parent;
this.dataStore = dataStore;
this.checkbox = null;
this.checked = trim.checked;
this.initCheckbox();
}
initCheckbox() {
const makeListBlock = document.createElement('div');
const dynamicId = `autocart_${this.value.toLowerCase().replace(/\s/g, '-').replace(/\./g, '')}`;
const dynamicClass = `autocart_term_${this.value.toLowerCase().replace(/\s/g, '-').replace(/[^a-zA-Z0-9-]/g, '')}`;
// const dynamicClass = `autocart_term_${sanitizeClassName(this.value)}`;
makeListBlock.innerHTML = `
<li class="w-full rounded-t-lg flex items-center justify-between transition duration-400 autocart-filters">
<div class="flex items-center mdc-form-field flex-wrap justify-between items-start flex-row w-full ${dynamicClass} hover:text-red-500">
<label for="${dynamicId}" class="text-sm font-medium text-gray-900 dark:text-gray-300 transition duration-400 font-normal text-base">${this.value}</label>
<span class="autocart_count text-gray-500 ml-2 transition duration-400 font-normal text-base">
(${this.count})
</span>
</div>
<div class="mdc-checkbox mdc-theme-primary">
<input type="checkbox" class="mdc-checkbox__native-control ${dynamicClass}" id="${dynamicId}" />
<div class="mdc-checkbox__background rounded-md">
<svg class="mdc-checkbox__checkmark" viewBox="0 0 24 24">
<path class="mdc-checkbox__checkmark-path" fill="none" d="M1.73,12.91 8.1,19.28 22.79,4.59"/>
</svg>
<div class="mdc-checkbox__mixedmark"></div>
</div>
<div class="mdc-checkbox__ripple p-2"></div>
</div>
</li>
`;
this.checkbox = makeListBlock.querySelector('.mdc-checkbox');
if (this.checkbox) {
// Initialize the MDCCheckbox with the actual checkbox element
const checkboxElement = this.checkbox.querySelector(`.mdc-checkbox__native-control.${dynamicClass}`);
this.checkbox = new mdc.checkbox.MDCCheckbox(this.checkbox);
this.formField = new mdc.formField.MDCFormField(makeListBlock.querySelector(`.mdc-form-field.${dynamicClass}`));
this.formField.input = checkboxElement;
this.checkbox.listen('change', () => this.handleCheckboxChange());
} else {
console.error('Checkbox element not found:', dynamicId);
}
this.markCheckboxIfValueFiltered()
return makeListBlock;
}
markCheckboxIfValueFiltered() {
let ifAlreadyFiltered = this.dataStore.getAppliedFilters().find(filter=>filter.type == "trim");
if(ifAlreadyFiltered){
if(ifAlreadyFiltered.values.includes(this.value)){
this.checkbox.checked = true;
}
}
}
handleCheckboxChange() {
shouldUpdateFilters = false;
const isChecked = this.checkbox.checked;
if (isChecked) {
this.dataStore.addFilter('trim', this.value);
} else {
this.dataStore.removeFilter('trim', this.value);
}
this.checked = isChecked;
}
setParentFilter(value){
this.isParentSelected = value;
this.generateCardHTML();
}
uncheckCheckbox() {
if (this.checkbox) {
this.checkbox.checked = false;
this.handleCheckboxChange();
// this.checkbox.dispatchEvent(new Event('change'));
}
}
}

View File

@@ -0,0 +1,62 @@
class VehicleThumbnail {
constructor(vehicle) {
this.id = vehicle.id_vehicle;
this.name = vehicle.make + ' ' + vehicle.model;
this.year = vehicle.year;
this.make = vehicle.make;
this.model = vehicle.model;
this.trim = vehicle.trim;
this.landingImage = vehicle.landingImage;
this.advertisePrice = vehicle.advertise_price.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
this.stockNumber = vehicle.stock_number;
this.mileage = vehicle.mileage;
}
generateCardElement() {
const wrapper = document.createElement('div');
wrapper.innerHTML = this.generateCardHTML().trim();
// Get the vehicle-card element
const cardElement = wrapper.querySelector('.vehicle-card');
const ripple = new mdc.ripple.MDCRipple(cardElement);
ripple.unbounded = false;
cardElement.addEventListener('click', () => {
console.log(dataStore)
var queryString = encodeURIComponent(JSON.stringify(dataStore.filters));
const url = `vehicle-details/?vehicle_id=${encodeURIComponent(this.id)}&filters=${queryString}`;
window.location.href = url;
});
return cardElement;
}
generateCardHTML() {
return `
<div class="bg-white shadow-md rounded-md transition-all duration-500 vehicle-card cursor-pointer mdc-ripple-surface hover:shadow-lg" data-vehicle-id="${this.id}">
<img src="${this.landingImage}" alt="Vehicle Image" class="w-full h-auto object-cover transition-all duration-500 vehicle-image rounded-tl-md rounded-tr-md">
<div class="flex flex-col justify-around transition-all duration-500 p-4">
<h3 class="text-lg font-semibold mb-2 break-words ">${this.year} ${this.name} ${this.trim.join(' - ')}</h3>
<div class="text-gray-600">Year: ${this.year}</div>
<div class="text-gray-600">Mileage: ${this.mileage} km</div>
<div class="text-red-800 font-bold mt-2">Price: $${this.advertisePrice}</div>
<div class="text-gray-600">Stock #: ${this.stockNumber} km</div>
</div>
</div>
`;
}
}
// Initialize the ripple component for all instances of VehicleThumbnail
document.addEventListener('DOMContentLoaded', function () {
const vehicleThumbnails = document.querySelectorAll('.mdc-ripple-surface');
vehicleThumbnails.forEach(thumbnail => {
const ripple = new mdc.ripple.MDCRipple(thumbnail);
ripple.unbounded = true;
});
});

281
autocart_assets/js/index.js Normal file
View File

@@ -0,0 +1,281 @@
let drawer;
let keyword;
let price;
let year;
let kilometres;
let dataStore;
let filterManager;
let appliedFilters;
let shouldUpdateFilters = true;
let modelFilter = [];
let makeFilters;
function sendReq() {
var formData = {
'action': 'handle_autocart_form',
'minPrice': price.getValueStart(),
'maxPrice': price.getValue(),
'minKilometres': kilometres.getValueStart(),
'maxKilometres': kilometres.getValue(),
'minYear': year.getValueStart(),
'maxYear': year.getValue(),
'autocart_nonce': jQuery('#autocart_nonce').val(),
};
const vehiclesContainer = document.querySelector('.vehicles-container');
vehiclesContainer.textContent = 'Loading...';
const filtersContainer = document.querySelector('.make-container');
filtersContainer.textContent = 'Loading...';
jQuery.ajax({
type: 'POST',
url: ajax_object.ajax_url,
data: formData,
success: function (response) {
const vehicles = response.data.vehicles;
if (dataStore) {
dataStore.unsubscribeAll();
}
// Initialize new data store
dataStore.vehicles = vehicles;
filterManager = new FilterManager(dataStore);
appliedFilters = new AppliedFilters(dataStore, filterManager);
// filterManager = new FilterManager(dataStore);
// appliedFilters = new AppliedFilters(dataStore, filterManager);
shouldUpdateFilters = true;
dataStore.subscribe(filteredVehicles => {
console.log('dataStore has been changed')
handleSuccess(filteredVehicles);
});
dataStore.notifySubscribers(vehicles);
},
error: function (xhr, status, error) {
console.log(xhr.responseText);
console.log('Status: ' + status);
console.log('Error: ' + error);
jQuery("#inventory-search__search-button").text("Error");
}
});
}
function handleSuccess(vehicles, updateFilters = shouldUpdateFilters) {
if(updateFilters){
createMakeList(vehicles);
createBodyTypeList(vehicles);
createTransmissionList(vehicles);
createExteriorList(vehicles);
}
const vehiclesContainer = document.querySelector('.vehicles-container');
vehiclesContainer.innerHTML = '';
vehicles.forEach(vehicleData => {
const vehicle = new VehicleThumbnail(vehicleData);
// vehiclesContainer.appendChild(vehicle.generateCardHTML());
vehiclesContainer.appendChild(vehicle.generateCardElement());
});
}
function createMakeList(vehicles){
const makeContainer = document.querySelector('.make-container');
makeContainer.innerHTML = '';
const makeCounts = vehicles.reduce((acc, vehicle) => {
const { make } = vehicle;
acc[make] = (acc[make] || 0) + 1;
return acc;
}, {});
let makeValue = document.querySelector('#autocart-make-field').value;
// if(makeValue){
// console.log('makeValue',makeValue);
// // dataStore.addFilter('make', makeValue);
// }
makeFilters = Object.entries(makeCounts).map(([make, count]) => {
return filterManager.createMakeFilter({
text: make,
checked: makeValue.toLowerCase() === make.toLowerCase(),
count,
models: fetchAvailableModels(vehicles, make)
}, dataStore);
});
makeFilters.forEach(make => {
makeContainer.appendChild(make.generateCardHTML());
make.subscribe(m=> initModelContainer(m))
});
}
function createBodyTypeList(vehicles){
const bodyTypeContainer = document.querySelector('.body-type-container');
bodyTypeContainer.innerHTML = '';
const bodyType = vehicles.reduce((acc, vehicle) => {
const { body_type } = vehicle;
acc[body_type] = (acc[body_type] || 0) + 1;
return acc;
}, {});
filterManager.resetList('body_type');
bodyTypeFilters = Object.entries(bodyType).map(([body_type, count]) => filterManager.createBodyTypeFilter({ text: body_type, count}, dataStore));
bodyTypeFilters.forEach(body_type => {
bodyTypeContainer.appendChild(body_type.initCheckbox());
// body_type.subscribe(m=> iniBodyTypeContainer(m))
});
}
function createTransmissionList(vehicles){
const transmissionContainer = document.querySelector('.transmission-container');
transmissionContainer.innerHTML = '';
const transmission = vehicles.reduce((acc, vehicle) => {
const { transmission_type } = vehicle;
acc[transmission_type] = (acc[transmission_type] || 0) + 1;
return acc;
}, {});
filterManager.resetList('transmission_type');
transmissionFilters = Object.entries(transmission).map(([transmission_type, count]) => filterManager.createTransmissionFilter({ text: transmission_type, count}, dataStore));
transmissionFilters.forEach(transmission => {
transmissionContainer.appendChild(transmission.initCheckbox());
});
}
function createExteriorList(vehicles){
console.log(vehicles);
const exteriorContainer = document.querySelector('.exterior-color');
exteriorContainer.innerHTML = '';
const exterior = vehicles.reduce((acc, vehicle) => {
const { exterior_color } = vehicle;
acc[exterior_color] = (acc[exterior_color] || 0) + 1;
return acc;
}, {});
filterManager.resetList('exterior_color');
exteriorFilters = Object.entries(exterior).map(([exterior_color, count]) => filterManager.createExteriorFilter({ text: exterior_color, count}, dataStore));
exteriorFilters.forEach(exterior => {
exteriorContainer.appendChild(exterior.initCheckbox());
});
}
function initModelContainer(make){
let list = [...new Set(filterManager.makeFilters.filter(m=>m.checked == true).map(m=>m.models).flat())];
const modelContainer = document.querySelector('.model-container');
if (list.length === 0) {
modelContainer.innerHTML = '<p>Please Select a Make</p>';
} else if (list && list.length > 0) {
modelContainer.innerHTML = '';
list.forEach(model => {
modelContainer.appendChild(model.initCheckbox());
});
} else {
modelContainer.innerHTML = '<p>No models available</p>';
}
}
function initTrimContainer(model){
let list = [...new Set(filterManager.modelFilters.filter(m=>m.checked == true).map(m=>m.trims).flat())];
// const uniqueArray = [...new Set(array)];
const trimContainer = document.querySelector('.trim-container');
if (list.length === 0) {
trimContainer.innerHTML = '<p>Please Select a Model</p>';
} else if (list && list.length > 0) {
trimContainer.innerHTML = '';
list.forEach(trim => {
trimContainer.appendChild(trim.initCheckbox());
});
} else {
trimContainer.innerHTML = '<p>No trims available</p>';
}
}
function fetchAvailableModels(vehicles, make) {
const filteredModels = vehicles
.filter(vehicle => vehicle.make === make)
.map(vehicle => vehicle.model);
// Count occurrences of each model
const modelCounts = filteredModels.reduce((acc, model) => {
acc[model] = (acc[model] || 0) + 1;
return acc;
}, {});
// Convert to an array of objects
const modelsArray = Object.entries(modelCounts).map(([model, count]) => ({ model, count }));
modelsArray.map(m=>m['trims'] = fetchAvailableTrim(vehicles, m.model))
return modelsArray;
}
function fetchAvailableTrim(vehicles, modelName){
const filteredVehicles = vehicles.filter(vehicle => vehicle.model === modelName);
const trimCount = filteredVehicles.reduce((result, vehicle) => {
vehicle.trim.forEach(trim => {
const existingTrim = result.find(item => item.text === trim);
if (existingTrim) {
existingTrim.count += 1;
} else {
result.push({ text: trim, count: 1 });
}
});
return result;
}, []);
return trimCount;
}
function updateModel(models){
console.log('updateModel', models)
}
function debounce(func, delay) {
let timeout;
return function () {
const context = this;
const args = arguments;
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(context, args), delay);
};
}
function updatePrice() {
const min = price.getValueStart();
const max = price.getValue();
const formattedMin = '$' + min.toLocaleString();
const formattedMax = '$' + max.toLocaleString();
document.getElementById('priceRange').textContent = `${formattedMin} to ${formattedMax}`;
debouncedSendReq();
}
function updateYear() {
const min = year.getValueStart();
const max = year.getValue();
document.getElementById('yearRange').textContent = `${min} to ${max}`;
debouncedSendReq();
}
function updateMileage() {
const min = kilometres.getValueStart();
const max = kilometres.getValue();
document.getElementById('KilometresRange').textContent = `${min} to ${max}`;
debouncedSendReq();
}
// Create debounced versions of sendReq
const debouncedSendReq = debounce(sendReq, 300);

View File

@@ -0,0 +1,300 @@
<?php
/*
Template Name: Autocart Results
*/
get_header();
// Get filter values from URL parameters
$minPrice = isset($_GET['minPrice']) ? sanitize_text_field($_GET['minPrice']): null;
$maxPrice = isset($_GET['maxPrice']) ? sanitize_text_field($_GET['maxPrice']):null;
$minKilometres = isset($_GET['minKilometres']) ? sanitize_text_field($_GET['minKilometres']): null;
$maxKilometres = isset($_GET['maxKilometres']) ? sanitize_text_field($_GET['maxKilometres']):null;
$make = isset($_GET['make']) ? sanitize_text_field($_GET['make']):null;
$minYear = isset($_GET['minYear']) ? (int)sanitize_text_field($_GET['minYear']):null;
$maxYear = isset($_GET['maxYear']) ? (int)sanitize_text_field($_GET['maxYear']):null;
// echo '<pre>';var_dump($minYear);echo '<br>'; var_dump($maxYear);exit;
$startYear = 2012;
$endYear = (int)date('Y');
// Exit the script
// exit;
// Generate nonce
$ajax_nonce = wp_create_nonce("update-filters-ajax-nonce");
$response = get_transient("autocart_response");
?>
<!-- Main Content -->
<div class="flex-grow flex max-w-7xl mx-auto">
<!-- Left Drawer for Filters -->
<aside class="w-1/4 p-4 shadow-lg bg-white hidden md:block rounded">
<div class="mdc-select mdc-select--outlined .col-xl-3 rounded p-2 w-full" id="order-by">
<div class="mdc-select__anchor">
<span class="mdc-notched-outline">
<span class="mdc-notched-outline__leading"></span>
<span class="mdc-notched-outline__notch" style="border-left: none; border-right: none;">
<span id="outlined-select-label" class="mdc-floating-label">Sort By</span>
</span>
<span class="mdc-notched-outline__trailing"></span>
</span>
<span class="mdc-select__selected-text-container">
<span class="mdc-select__selected-text"></span>
</span>
<span class="mdc-select__dropdown-icon">
<svg class="mdc-select__dropdown-icon-graphic" viewBox="7 10 10 5" focusable="false">
<polygon class="mdc-select__dropdown-icon-inactive" stroke="none" fill-rule="evenodd" points="7 10 12 15 17 10"></polygon>
<polygon class="mdc-select__dropdown-icon-active" stroke="none" fill-rule="evenodd" points="7 15 12 10 17 15"></polygon>
</svg>
</span>
</div>
<div class="mdc-select__menu mdc-menu mdc-menu-surface mdc-menu-surface--fullwidth">
<ul class="mdc-list" role="listbox" aria-label="Sort By" id="sort-options-list">
<!-- Options will be dynamically added here using JavaScript -->
</ul>
</div>
</div>
<div class="space-y-2">
<!-- Panel 1 -->
<div class="border rounded p-2">
<div class="flex justify-between items-center p-4">
<div class="w-full text-left focus:outline-none transition"> Price </div>
<svg class="w-4 h-4 hover:bg-gray-200" fill="none" stroke="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 9l-7 7-7-7"></path>
</svg>
</div>
<div class="mt-2">
<div class="flex justify-center items-center">
<span id="priceRange" class="slider-readings"> <?php echo '$' . number_format($minPrice) . ' to $' . number_format($maxPrice) ?> </span></div>
<div class="mdc-slider mdc-slider--range" id="autocart-price-range-field">
<input class="mdc-slider__input" type="range" min="10000" max="80000" value="
<?php echo isset($minPrice)? $minPrice: 10000 ?>" name="rangeStart" aria-label="Continuous range slider demo">
<input class="mdc-slider__input" type="range" min="10000" max="80000" value="<?php echo isset($maxPrice)? $maxPrice: 80000 ?>" name="rangeEnd" aria-label="Continuous range slider demo">
<div class="mdc-slider__track">
<div class="mdc-slider__track--inactive"></div>
<div class="mdc-slider__track--active">
<div class="mdc-slider__track--active_fill"></div>
</div>
</div>
<div class="mdc-slider__thumb">
<div class="mdc-slider__thumb-knob"></div>
</div>
<div class="mdc-slider__thumb">
<div class="mdc-slider__thumb-knob"></div>
</div>
</div>
</div>
</div>
<!-- Panel 2 -->
<div class="border rounded p-2">
<div class="flex justify-between items-center p-4">
<div class="w-full text-left focus:outline-none transition"> Year </div>
<svg class="w-4 h-4 hover:bg-gray-200" fill="none" stroke="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 9l-7 7-7-7"></path>
</svg>
</div>
<div class="mt-2">
<div class="flex justify-center items-center">
<span id="yearRange" class="slider-readings"> <?php echo $minYear .' to '.$maxYear; ?> </span>
</div>
<div class="mdc-slider mdc-slider--range" id="autocart-year-range-field">
<input class="mdc-slider__input" type="range" min="<?php echo $startYear ?>" max="<?php echo $endYear ?>" value="<?php echo isset($minYear) ? $minYear : $startYear ?>" name="rangeStart" aria-label="Continuous range slider demo">
<input class="mdc-slider__input" type="range" min="<?php echo $startYear ?>" max="<?php echo $endYear ?>" value="<?php echo isset($maxYear) ? $maxYear : $endYear ?>" name="rangeEnd" aria-label="Continuous range slider demo">
<div class="mdc-slider__track">
<div class="mdc-slider__track--inactive"></div>
<div class="mdc-slider__track--active">
<div class="mdc-slider__track--active_fill"></div>
</div>
</div>
<div class="mdc-slider__thumb">
<div class="mdc-slider__thumb-knob"></div>
</div>
<div class="mdc-slider__thumb">
<div class="mdc-slider__thumb-knob"></div>
</div>
</div>
</div>
</div>
<!-- Panel 3 -->
<div class="border rounded p-2">
<div class="flex justify-between items-center p-4">
<div class="w-full text-left focus:outline-none transition"> Kilometres </div>
<svg class="w-4 h-4 hover:bg-gray-200" fill="none" stroke="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 9l-7 7-7-7"></path>
</svg>
</div>
<div class="mt-2">
<div class="flex justify-center items-center">
<span id="KilometresRange" class="slider-readings">19000 to 222000 </span>
</div>
<div class="mdc-slider mdc-slider--range" id="autocart-kilometres-range-field">
<input class="mdc-slider__input" type="range" min="19000" max="222000" value="19000" name="rangeStart" aria-label="Continuous range slider demo">
<input class="mdc-slider__input" type="range" min="19000" max="222000" value="222000" name="rangeEnd" aria-label="Continuous range slider demo">
<div class="mdc-slider__track">
<div class="mdc-slider__track--inactive"></div>
<div class="mdc-slider__track--active">
<div class="mdc-slider__track--active_fill"></div>
</div>
</div>
<div class="mdc-slider__thumb">
<div class="mdc-slider__thumb-knob"></div>
</div>
<div class="mdc-slider__thumb">
<div class="mdc-slider__thumb-knob"></div>
</div>
</div>
</div>
</div>
<!-- Panel 4 -->
<div class="border rounded p-2">
<div class="flex justify-between items-center p-4">
<div class="w-full text-left focus:outline-none transition"> Make </div>
<svg class="w-4 h-4 hover:bg-gray-200" fill="none" stroke="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 9l-7 7-7-7"></path>
</svg>
</div>
<div class="mt-2">
<ul class="make-container list-none text-gray-900 w-48 bg-white text-sm font-medium rounded-lg w-full">
</ul>
</div>
</div>
<div class="border rounded p-2">
<div class="flex justify-between items-center p-4">
<div class="w-full text-left focus:outline-none transition"> Model </div>
<svg class="w-4 h-4 hover:bg-gray-200" fill="none" stroke="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 9l-7 7-7-7"></path>
</svg>
</div>
<div class="mt-2">
<ul class="model-container list-none text-gray-900 w-48 bg-white text-sm font-medium rounded-lg w-full">
<p>Please Select a Make.</p>
</ul>
</div>
</div>
<div class="border rounded p-2">
<div class="flex justify-between items-center p-4">
<div class="w-full text-left focus:outline-none transition"> Trim </div>
<svg class="w-4 h-4 hover:bg-gray-200" fill="none" stroke="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 9l-7 7-7-7"></path>
</svg>
</div>
<div class="mt-2">
<ul class="trim-container list-none text-gray-900 bg-white text-sm font-medium rounded-lg w-full">
<p>Please Select a Model.</p>
</ul>
</div>
</div>
<div class="border rounded p-2">
<div class="flex justify-between items-center p-4">
<div class="w-full text-left focus:outline-none transition"> Body Style </div>
<svg class="w-4 h-4 hover:bg-gray-200" fill="none" stroke="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 9l-7 7-7-7"></path>
</svg>
</div>
<div class="mt-2">
<ul class="body-type-container list-none text-gray-900 bg-white text-sm font-medium rounded-lg w-full">
</ul>
</div>
</div>
<div class="border rounded p-2">
<div class="flex justify-between items-center p-4">
<div class="w-full text-left focus:outline-none transition"> Transmission </div>
<svg class="w-4 h-4 hover:bg-gray-200" fill="none" stroke="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 9l-7 7-7-7"></path>
</svg>
</div>
<div class="mt-2">
<ul class="transmission-container list-none text-gray-900 bg-white text-sm font-medium rounded-lg w-full">
</ul>
</div>
</div>
<div class="border rounded p-2">
<div class="flex justify-between items-center p-4">
<div class="w-full text-left focus:outline-none transition"> Exterior Color </div>
<svg class="w-4 h-4 hover:bg-gray-200" fill="none" stroke="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M19 9l-7 7-7-7"></path>
</svg>
</div>
<div class="mt-2">
<ul class="exterior-color list-none text-gray-900 bg-white text-sm font-medium rounded-lg w-full">
</ul>
</div>
</div>
</div>
<!-- Add more filters as needed -->
</aside>
<!-- Main Content -->
<div class="w-3/4 ml-1/4 p-4 pb-0 flex-grow">
<!-- Top App Bar with Menu Button -->
<header class="shadow-lg bg-white p-4 flex justify-between items-center rounded">
<div class="mt-2" id="applied-filters-block"></div>
<div class="flex items-center space-x-4">
<i id="grid-view" class="fas fa-th text-gray-500 cursor-pointer transition duration-300 transform hover:scale-110 hover:shadow-md ripple selected"></i>
<i id="list-view" class="fas fa-list text-gray-500 cursor-pointer transition duration-300 transform hover:scale-110 hover:shadow-md ripple"></i>
</div>
</header>
<!-- Vehicle List -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mt-4 vehicles-container">
<!-- Vehicle Cards go here -->
</div>
</div>
</div>
<?php
wp_nonce_field('autocart_nonce', 'autocart_nonce'); ?> <?php get_footer();
if ($make) {
echo "<input type='hidden' id='autocart-make-field' value='$make'>";
} ?>
<script>
document.addEventListener('DOMContentLoaded', function () {
price = new mdc.slider.MDCSlider(document.querySelector('#autocart-price-range-field'));
year = new mdc.slider.MDCSlider(document.querySelector('#autocart-year-range-field'));
kilometres = new mdc.slider.MDCSlider(document.querySelector('#autocart-kilometres-range-field'));
price.listen('MDCSlider:input', updatePrice);
year.listen('MDCSlider:input', updateYear);
kilometres.listen('MDCSlider:input', updateMileage);
new DisplayMode();
dataStore = new DataStore([]);
new SortBy(dataStore);
sendReq();
});
</script>

View File

@@ -0,0 +1,455 @@
<?php
$priceRanges = [
[
'text' => 'All Prices',
'value' => [10000, 80000],
],
[
'text' => 'Under $10,000',
'value' => [0, 10000],
],
[
'text' => '$10,000 - $19,999',
'value' => [10000, 19000],
],
[
'text' => '$20,000 - $29,999',
'value' => [20000, 29000],
],
[
'text' => '$30,000 - $39,999',
'value' => [30000, 39000],
],
[
'text' => '$40,000 - $49,999',
'value' => [40000, 49000],
],
[
'text' => '$50,000 - $59,999',
'value' => [50000, 59000],
],
[
'text' => '$60,000 - $69,999',
'value' => [60000, 69000],
],
[
'text' => '$70,000 - $79,999',
'value' => [70000, 80000],
],
];
$startYear = 2012;
$endYear = (int)date('Y');
$years = range($startYear, $endYear);
array_unshift($years, 'All Years');
// Transform the array into an array of objects
$years = array_map(function ($year) use ($startYear, $endYear) {
if ($year == 'All Years') {
return ['text' => $year, 'value' => [$startYear, $endYear]];
} else {
return ['text' => $year, 'value' => [$year, $year]];
}
}, $years);
$carMakes = [
"All Makes", "Acura", "Audi", "BMW", "Ford", "GMC",
"Honda", "Hyundai", "Jaguar", "Jeep", "Kia",
"Lexus", "Mazda", "Mercedes-Benz", "Tesla", "Toyota", "Volkswagen"
];
?>
<form id="vehicle-search-form" name="vehicle-search-form" method="get" action="<?php echo esc_url(home_url('/index.php/autocart-results')); ?>" class="w-full bg-white px-6 lg:px-8 py-10 rounded-md">
<h2 class="text-xl lg:text-3xl font-bold mb-2 uppercase">Find your vehicle</h2>
<div class="sm:grid sm:grid-cols-2 gap-4">
<div>
<label class="block mb-2 text-sm font-medium text-gray-700" for="vehicle_search_make">Make</label>
<select class="w-full px-3 py-2 border rounded-md text-gray-700 focus:outline-none focus:border-red-500" id="vehicle_search_make" name="vehicle_search_make">
<?php foreach ($carMakes as $index => $make) : ?>
<option value="<?= strtolower(str_replace(' ', '', $make)); ?>" <?= $index === 0 ? 'selected' : ''; ?>>
<?= $make; ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div>
<label class="block mb-2 text-sm font-medium text-gray-700" for="vehicle_search_model">Model</label>
<select class="w-full px-3 py-2 border rounded-md text-gray-700 focus:outline-none focus:border-red-500" id="vehicle_search_model" name="vehicle_search_model">
<option value="" disabled="" selected="">Select Model</option>
<!-- <option value="Camry">Camry</option>
<option value="Accord">Accord</option>
<option value="F-150">F-150</option>
<option value="Silverado">Silverado</option>
<option value="Altima">Altima</option> -->
</select>
</div>
<div>
<label class="block mb-2 text-sm font-medium text-gray-700" for="vehicle_search_year">Year</label>
<select class="w-full px-3 py-2 border rounded-md text-gray-700 focus:outline-none focus:border-red-500" id="vehicle_search_year" name="vehicle_search_year">
<?php foreach ($years as $index => $year) : ?>
<option value="<?= htmlspecialchars(json_encode($year['value'])); ?>" <?= $index === 0 ? 'selected' : ''; ?>>
<?= $year['text']; ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div>
<label class="block mb-2 text-sm font-medium text-gray-700" for="vehicle_search_body_type">Body Type</label>
<select class="w-full px-3 py-2 border rounded-md text-gray-700 focus:outline-none focus:border-red-500" id="vehicle_search_body_type" name="vehicle_search_body_type">
<option value="" disabled="" selected="">Select Body Type</option>
<!-- <option value="Sedan">Sedan</option>
<option value="SUV">SUV</option>
<option value="Truck">Truck</option>
<option value="Hatchback">Hatchback</option>
<option value="Coupe">Coupe</option> -->
</select>
</div>
<div>
<label class="block mb-2 text-sm font-medium text-gray-700" for="vehicle_search_transmission">Transmission</label>
<select class="w-full px-3 py-2 border rounded-md text-gray-700 focus:outline-none focus:border-red-500" id="vehicle_search_transmission" name="vehicle_search_transmission">
<option value="" disabled="" selected="">Select Transmission</option>
<!-- <option value="Automatic">Automatic</option>
<option value="Manual">Manual</option>
<option value="CVT">CVT</option> -->
</select>
</div>
<div>
<label class="block mb-2 text-sm font-medium text-gray-700" for="vehicle_search_trim">Trim</label>
<select class="w-full px-3 py-2 border rounded-md text-gray-700 focus:outline-none focus:border-red-500" id="vehicle_search_trim" name="vehicle_search_trim">
<option value="" disabled="" selected="">Select Trim</option>
<!-- <option value="LE">LE</option>
<option value="EX">EX</option>
<option value="XLT">XLT</option>
<option value="LT">LT</option>
<option value="SV">SV</option> -->
</select>
</div>
<div class="flex flex-col col-span-2">
<div>
<label class="block mb-2 text-sm font-medium text-gray-700" for="autocart-kilometres-range-field">Kilometers</label>
<!-- <input class="w-full px-3 py-2 border rounded-md text-gray-700 focus:outline-none focus:border-red-500" type="hidden" id="autocart-kilometres-range-field" name="autocart-kilometres-range-field" min="16000" max="200000"> -->
</div>
<div class="range-container">
<div class="mdc-slider mdc-slider--range" id="autocart-kilometres-range-field">
<input class="mdc-slider__input" type="range" min="19000" max="222000" value="19000" name="rangeStart" aria-label="Continuous range slider demo">
<input class="mdc-slider__input" type="range" min="19000" max="222000" value="222000" name="rangeEnd" aria-label="Continuous range slider demo">
<div class="mdc-slider__track">
<div class="mdc-slider__track--inactive"></div>
<div class="mdc-slider__track--active">
<div class="mdc-slider__track--active_fill"></div>
</div>
</div>
<div class="mdc-slider__thumb">
<div class="mdc-slider__thumb-knob"></div>
</div>
<div class="mdc-slider__thumb">
<div class="mdc-slider__thumb-knob"></div>
</div>
</div>
<label class="block mb-2 text-sm font-medium text-white-700 range-label" for="autocart-kilometres-range-field">19000 to 222000</label>
</div>
<div>
<label class="block mb-2 text-sm font-medium text-gray-700" for="autocart-price-range-field">Price</label>
<!-- <input class="w-full px-3 py-2 border rounded-md text-gray-700 focus:outline-none focus:border-red-500" type="hidden" id="vehicle_search_price" name="vehicle_search_price" min="5000" max="70000"> -->
</div>
<div class="range-container">
<?php
$minPrice = $priceRanges[0]['value'][0];
$maxPrice = $priceRanges[count($priceRanges) - 1]['value'][1]; ?>
<div class="mdc-slider mdc-slider--range" id="autocart-price-range-field">
<input class="mdc-slider__input" type="range" min="<?php echo $minPrice; ?>" max="<?php echo $maxPrice; ?>" value="<?php echo isset($minPrice) ? $minPrice : 10000; ?>" name="rangeStart" aria-label="Continuous range slider demo">
<input class="mdc-slider__input" type="range" min="<?php echo $minPrice; ?>" max="<?php echo $maxPrice; ?>" value="<?php echo isset($maxPrice) ? $maxPrice : 80000; ?>" name="rangeEnd" aria-label="Continuous range slider demo">
<div class="mdc-slider__track">
<div class="mdc-slider__track--inactive"></div>
<div class="mdc-slider__track--active">
<div class="mdc-slider__track--active_fill"></div>
</div>
</div>
<div class="mdc-slider__thumb">
<div class="mdc-slider__thumb-knob"></div>
</div>
<div class="mdc-slider__thumb">
<div class="mdc-slider__thumb-knob"></div>
</div>
</div>
<label class="block mb-2 text-sm font-medium text-white-700 range-label" for="autocart-price-range-field"><?php echo '$' . number_format($minPrice) . ' to $' . number_format($maxPrice) ?></label>
</div>
</div>
<input type="hidden" name="maxPrice" id="autocart-max-price-field-input" />
<input type="hidden" name="minPrice" id="autocart-min-price-field-input" />
<input type="hidden" name="maxYear" id="autocart-max-year-field-input" />
<input type="hidden" name="minYear" id="autocart-min-year-field-input" />
<input type="hidden" name="make" id="autocart-make-field-input" />
<?php wp_nonce_field('autocart_nonce', 'autocart_nonce'); ?>
<div class="lg:col-span-2">
<button type="submit" aria-label="Find Your Vehicle Search" class="w-full bg-red-500 hover:bg-red-800 transition-colors duration-300 text-white font-medium py-2 px-4 rounded" id="inventory-search-button">
<i class="bi-search mr-2"></i>Search
</button>
</div>
</div>
</form>
<script>
let priceWidget;
document.addEventListener('DOMContentLoaded', function() {
priceWidget = new mdc.slider.MDCSlider(document.querySelector('#autocart-price-range-field'));
priceWidget.listen('MDCSlider:input', updatePiceWidget);
kilometresWidget = new mdc.slider.MDCSlider(document.querySelector('#autocart-kilometres-range-field'));
kilometresWidget.listen('MDCSlider:input', updateMileageWidget);
const year = document.querySelector('#vehicle_search_year');
const make = document.querySelector('#vehicle_search_make');
const model = document.querySelector('#vehicle_search_model');
// Initial AJAX request on document ready
handleFieldChange([{
property: 'body_type',
name: 'Body Type',
selector: '#vehicle_search_body_type'
},
{
property: 'transmission_type',
name: 'Transmission',
selector: '#vehicle_search_transmission'
},
{
property: 'make',
name: 'Make',
selector: '#vehicle_search_make'
},
{
property: 'model',
name: 'Model',
selector: '#vehicle_search_model'
}
]);
year.addEventListener('change', function() {
debouncedHandleFieldChange([{
property: 'body_type',
name: 'Body Type',
selector: '#vehicle_search_body_type'
},
{
property: 'transmission_type',
name: 'Transmission',
selector: '#vehicle_search_transmission'
},
{
property: 'make',
name: 'Make',
selector: '#vehicle_search_make'
},
{
property: 'model',
name: 'Model',
selector: '#vehicle_search_model'
}
])
});
make.addEventListener('change', function() {
debouncedHandleFieldChange([{
property: 'model',
name: 'Model',
selector: '#vehicle_search_model'
},
{
property: 'body_type',
name: 'Body Type',
selector: '#vehicle_search_body_type'
},
{
property: 'transmission_type',
name: 'Transmission',
selector: '#vehicle_search_transmission'
},
])
});
model.addEventListener('change', function() {
debouncedHandleFieldChange([{
property: 'trim',
name: 'Tim',
selector: '#vehicle_search_trim'
}])
});
const btns = document.getElementsByClassName("ripple-effect");
for (const btn of btns) {
btn.addEventListener("click", rippleEffect);
}
});
function updateMileageWidget() {
document.querySelector('label.range-label[for="autocart-kilometres-range-field"]').textContent = `${kilometresWidget.getValueStart()} to ${kilometresWidget.getValue()}`;
debouncedHandleFieldChange();
}
function updatePiceWidget() {
document.querySelector('label.range-label[for="autocart-price-range-field"]').textContent = `${priceWidget.getValueStart()} to ${priceWidget.getValue()}`;
debouncedHandleFieldChange();
}
function debounce(func, delay) {
let timeout;
return function() {
const context = this;
const args = arguments;
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(context, args), delay);
};
}
const debouncedHandleFieldChange = debounce(
function(inputs) {
handleFieldChange(inputs);
}, 300);
function handleFieldChange(inputs = []) {
// const price = document.querySelector('#autocart-price-field');
const year = document.querySelector('#vehicle_search_year');
const make = document.querySelector('#vehicle_search_make');
// let priceRange = JSON.parse(priceWidget.value);
// let minPrice = priceRange[0];
// let maxPrice = priceRange[1];
let yearRange = JSON.parse(year.value);
console.log('yearRange', yearRange)
let minYear = yearRange[0];
let maxYear = yearRange[1];
document.getElementById('autocart-max-price-field-input').value = priceWidget.getValue();;
document.getElementById('autocart-min-price-field-input').value = priceWidget.getValueStart();
document.getElementById('autocart-max-year-field-input').value = maxYear;
document.getElementById('autocart-min-year-field-input').value = minYear;
document.getElementById('autocart-make-field-input').value = make.value;
// var formData = {
// 'action': 'handle_autocart_form',
// 'minPrice': priceWidget.getValueStart(),
// 'maxPrice': priceWidget.getValue(),
// 'make': make.value,
// 'minYear': minYear,
// 'maxYear': maxYear,
// 'autocart_nonce': jQuery('#autocart_nonce').val(),
// };
var formData = {
'action': 'handle_autocart_form',
'minPrice': priceWidget.getValueStart(),
'maxPrice': priceWidget.getValue(),
'minKilometres': kilometresWidget.getValueStart(),
'maxKilometres': kilometresWidget.getValue(),
'minYear': minYear,
'maxYear': maxYear,
'make': make.value,
'autocart_nonce': jQuery('#autocart_nonce').val(),
};
jQuery("#inventory-search-button").text("Loading...");
jQuery.ajax({
type: 'POST',
url: ajax_object.ajax_url,
data: formData,
success: function(response) {
const vehicles = response.data.vehicles;
if (inputs) {
inputs.forEach(element => {
createOptions(vehicles, element);
});
}
jQuery("#inventory-search-button").text("Search (" + vehicles.length + ")");
},
error: function(xhr, status, error) {
console.log(xhr.responseText);
console.log('Status: ' + status);
console.log('Error: ' + error);
jQuery("#inventory-search-button").text("Error");
}
});
}
function rippleEffect(event) {
const btn = event.currentTarget;
btn.classList.add("overflow-hidden", "shadow", "relative");
const circle = document.createElement("span");
const rect = btn.getBoundingClientRect();
const diameter = Math.max(btn.clientWidth, btn.clientHeight);
const radius = diameter / 2;
const offsetX = event.clientX - rect.left;
const offsetY = event.clientY - rect.top;
circle.style.width = circle.style.height = `${diameter}px`;
circle.style.left = `${offsetX - radius}px`;
circle.style.top = `${offsetY - radius}px`;
circle.classList.add("ripple");
const ripple = btn.getElementsByClassName("ripple")[0];
if (ripple) {
ripple.remove();
}
btn.appendChild(circle);
}
function createOptions(vehicles, input) {
if (vehicles.length) {
const element = document.querySelector(input.selector);
// Remove existing options only
const existingOptions = element.querySelectorAll('option');
existingOptions.forEach(option => option.remove());
const counts = vehicles.reduce((acc, vehicle) => {
const value = vehicle[input.property];
acc[value] = (acc[value] || 0) + 1;
return acc;
}, {});
// Sort the property values alphabetically
const sortedValues = Object.keys(counts).sort();
// Add "All" at the beginning
sortedValues.unshift(`All ${input.name}s`);
// Create and append options
sortedValues.forEach(value => {
var option = document.createElement("option");
option.text = value;
option.value = value;
option.style.textTransform = "capitalize";
element.appendChild(option);
});
}
}
</script>

View File

@@ -0,0 +1,689 @@
<?php
/*
Template Name: Vehicle Template
*/
get_header();
?>
<style>
.modal {
transition: opacity 0.25s ease;
}
body.modal-active {
overflow-x: hidden;
overflow-y: visible !important;
}
span.ripple {
position: absolute;
border-radius: 50%;
transform: scale(0);
animation: ripple 600ms linear;
background-color: rgba(255, 255, 255, 0.7);
}
@keyframes ripple {
to {
transform: scale(4);
opacity: 0;
}
}
</style>
<?php
// Retrieve the autocart response data
$vehicle_id = isset($_GET['vehicle_id']) ? urldecode($_GET['vehicle_id']) : '';
if ($vehicle_id) {
// Retrieve the external token from WordPress options
$external_token = get_option('autocart_server_token');
$pluginToken = get_option('autocart_plugin_token'); // Retrieve the token from where you stored it
if ($external_token) {
// Make an external request to get vehicle details
$vehicle_details_url = REMOTE_SERVER_URL . '/api/v1/wp/vehicle/' . $vehicle_id;
$vehicle_details_response = wp_remote_get($vehicle_details_url, array(
'headers' => array(
'Authorization' => $external_token,
'Plugin' => $pluginToken
),
));
// Make another request to get vehicle images
$vehicle_images_url = REMOTE_SERVER_URL . '/api/v1/companies/1/vehicles/' . $vehicle_id . '/images';
$vehicle_images_response = wp_remote_get($vehicle_images_url, array(
'headers' => array(
'Authorization' => $external_token,
'Plugin' => $pluginToken
),
));
$vehicle_features = REMOTE_SERVER_URL . '/api/v1/companies/1/vehicles/' . $vehicle_id . '/vehiclefeatures';
$vehicle_features_response = wp_remote_get($vehicle_features, array(
'headers' => array(
'Authorization' => $external_token,
'Plugin' => $pluginToken
),
));
if (
!is_wp_error($vehicle_details_response) && !is_wp_error($vehicle_images_response)
&& wp_remote_retrieve_response_code($vehicle_details_response) === 200
&& wp_remote_retrieve_response_code($vehicle_images_response) === 200
&& wp_remote_retrieve_response_code($vehicle_features_response) === 200
) {
// Decode and print the vehicle details response
$vehicle_details = json_decode(wp_remote_retrieve_body($vehicle_details_response), true);
$vehicle_features = json_decode(wp_remote_retrieve_body($vehicle_features_response), true);
// Decode and print the vehicle images response
$vehicle_images = json_decode(wp_remote_retrieve_body($vehicle_images_response), true);
// var_dump($searchData); exit;
$filters = array(
'body_type' => $vehicle_details['body_type']
);
$similar_vehicles_response = wp_remote_post(REMOTE_SERVER_URL . '/api/v1/wp', array(
'body' => json_encode($filters), // The filters array will be sent in the body of the request
'headers' => array(
'Content-Type' => 'application/json', // Make sure to set the Content-Type header appropriately
'Authorization' => $external_token,
'Plugin' => $pluginToken
),
));
if (!is_wp_error($similar_vehicles_response) && wp_remote_retrieve_response_code($similar_vehicles_response) === 200) {
$similar_vehicles = json_decode(wp_remote_retrieve_body($similar_vehicles_response), true);
$filtered_vehicles = array_filter($similar_vehicles, function ($vehicle) use ($vehicle_details) {
return $vehicle['id_vehicle'] !== $vehicle_details['id_vehicle'];
});
// echo '<pre>';
// print_r($filtered_vehicles);
// echo '</pre>';
}
// Initialize an empty array to store grouped features
$groupedFeatures = array();
// Loop through the features and group them by category
foreach ($vehicle_features as $feature) {
$category = $feature['category'];
// Check if the category key exists in the grouped array
if (!array_key_exists($category, $groupedFeatures)) {
// If not, create an empty array for the category
$groupedFeatures[$category] = array();
}
// Add the feature to the corresponding category
$groupedFeatures[$category][] = $feature;
}
// Now $groupedFeatures contains features grouped by category
// print_r($groupedFeatures);
// echo '<pre>';
// print_r($searchData);
// echo '</pre>';
} else {
echo '<div class="bg-gray-100 dark:bg-gray-800 py-8">
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex flex-col md:flex-row"><p>Error retrieving vehicle details or images.</p></div></div</div>';
exit;
}
} else {
echo '<div class="bg-gray-100 dark:bg-gray-800 py-8">
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex flex-col md:flex-row"><p>Error: External token not found.</p>/div></div</div>';
exit;
}
} else {
echo '<div class="bg-gray-100 dark:bg-gray-800 py-8">
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex flex-col md:flex-row"><p>No vehicle found.</p></div></div</div>';
exit;
} ?>
<?php
// Assuming you have the images in the $vehicle_images array
$image_title = !empty($vehicle_images[0]['title']) ? $vehicle_images[0]['title'] : '';
$image_description = !empty($vehicle_images[0]['description']) ? $vehicle_images[0]['description'] : '';
?>
<div class="max-w-7xl mx-auto">
<div class="grid grid-cols-2">
<div class="relative group cursor-pointer">
<img src="<?php echo $vehicle_images[0]['url']; ?>" alt="<?php echo $image_title; ?>" class="w-full h-full object-cover">
<div class="absolute inset-0 bg-black opacity-0 group-hover:opacity-75 transition duration-500 ease-in-out"></div>
<div class="absolute inset-0 bottom-0 p-4 text-white group-hover:opacity-100 transition duration-500 ease-in-out" onclick="showFeaturedImage(0);openFeaturedImage()">
<h3><?php echo $image_title; ?></h3>
<p><?php echo $image_description; ?></p>
</div>
</div>
<div class="relative grid grid-cols-2">
<?php
$end = (!empty($vehicle_images) && count($vehicle_images) >= 5) ? 5 : count($vehicle_images);
// Start loop from index 1 to skip the first image
for ($i = 1; $i < $end; $i++) {
$image = $vehicle_images[$i];
// Extract image URL, title, and description
$image_url = $image['url'];
?>
<div class="relative group cursor-pointer">
<img src="<?php echo $image_url; ?>" alt="<?php echo $image_title; ?>" class="w-full h-full object-cover">
<div class="absolute inset-0 bg-black opacity-0 group-hover:opacity-75 transition duration-500 ease-in-out"></div>
<div class="absolute inset-0 bottom-0 p-4 text-white group-hover:opacity-100 transition duration-500 ease-in-out" onclick="showFeaturedImage(<?php echo $i; ?>);openFeaturedImage()">
<h3><?php echo $image_title; ?></h3>
<p><?php echo $image_description; ?></p>
</div>
</div>
<?php
}
?>
</div>
</div>
<div class="bg-gray-100 dark:bg-gray-800 py-4">
<!-- Single Car Information -->
<div class="lg:grid grid-cols-3 relative">
<!-- Vehicle Details -->
<div class="mx-auto col-span-2">
<!-- Single Car -->
<div class="m-4">
<h2 class="text-2xl font-bold text-gray-800 dark:text-white mb-2">
<!-- title -->
<?php echo $vehicle_details['year'] . ' ' . $vehicle_details['make'] . ' ' . $vehicle_details['model'] . ' ' . $vehicle_details['trim'] . ' - ' . $vehicle_details['exterior_color'] . ' ' . $vehicle_details['interior_color']; ?>
</h2>
<p> Stock #: <?php echo $vehicle_details['stock_number'] ?></p>
<hr class="my-4 border-gray-300">
<span class="font-bold text-gray-700 dark:text-gray-300">About this vehicle</span>
<div class="flex items-center mt-4">
<div class="text-gray-900 bg-white border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600 dark:text-white">
<button type="button" class="relative inline-flex items-center w-full px-4 py-2 text-sm font-medium border-b border-gray-200 hover:bg-gray-100 hover:text-black-700 dark:border-gray-600 dark:hover:bg-gray-600 dark:hover:text-white text-black flex-row gap-2">
<i class="fas fa-tachometer-alt w-3 h-3 me-3"></i>
<span class="label">Kilometres</span>
<span class="value"><?php echo number_format($vehicle_details['mileage']) ?></span>
</button>
<button type="button" class="relative inline-flex items-center w-full px-4 py-2 text-sm font-medium border-b border-gray-200 hover:bg-gray-100 hover:text-black-700 dark:border-gray-600 dark:hover:bg-gray-600 dark:hover:text-white text-black flex-row gap-2">
<i class="fas fa-car-side w-3 h-3 me-3"></i>
<span class="label">Body Style</span>
<span class="value"><?php echo $vehicle_details['body_type'] ?></span>
</button>
<button type="button" class="relative inline-flex items-center w-full px-4 py-2 text-sm font-medium border-b border-gray-200 hover:bg-gray-100 hover:text-black-700 dark:border-gray-600 dark:hover:bg-gray-600 dark:hover:text-white text-black flex-row gap-2">
<i class="fas fa-cogs w-3 h-3 me-3"></i>
<span class="label">Engine</span>
<span class="value"><?php echo $vehicle_details['engine'] ?></span>
</button>
<button type="button" class="relative inline-flex items-center w-full px-4 py-2 text-sm font-medium border-b border-gray-200 hover:bg-gray-100 hover:text-black-700 dark:border-gray-600 dark:hover:bg-gray-600 dark:hover:text-white text-black flex-row gap-2">
<i class="fas fa-palette w-3 h-3 me-3"></i>
<span class="label">Exterior Colour</span>
<span class="value"><?php echo $vehicle_details['exterior_color'] ?></span>
</button>
<button type="button" class="relative inline-flex items-center w-full px-4 py-2 text-sm font-medium border-b border-gray-200 hover:bg-gray-100 hover:text-black-700 dark:border-gray-600 dark:hover:bg-gray-600 dark:hover:text-white text-black flex-row gap-2">
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88" width="20" height="20">
<path d="M61.44,0A61.46,61.46,0,1,1,18,18,61.23,61.23,0,0,1,61.44,0Zm4.07,82.09a6.67,6.67,0,1,1-8.14,0V68.62H42.31V82.09a6.67,6.67,0,1,1-8.14,0V46.17a6.67,6.67,0,1,1,8.14,0V60.48H57.37V46.17a6.67,6.67,0,1,1,8.14,0V60.48H80.57V46.17a6.67,6.67,0,1,1,8.14,0V64a4.41,4.41,0,0,1,0,.52,4.07,4.07,0,0,1-4.07,4.07H65.51V82.09Zm33-57.76a52.46,52.46,0,1,0,15.38,37.11A52.29,52.29,0,0,0,98.55,24.33Z" />
</svg>
<span class="label">Transmission</span>
<span class="value"><?php echo $vehicle_details['transmission_type'] ?></span>
</button>
<button type="button" class="relative inline-flex items-center w-full px-4 py-2 text-sm font-medium border-b border-gray-200 hover:bg-gray-100 hover:text-black-700 dark:border-gray-600 dark:hover:bg-gray-600 dark:hover:text-white text-black flex-row gap-2">
<i class="fas fa-users w-3 h-3 me-3"></i>
<span class="label"># of Passengers</span>
<span class="value"><?php echo $vehicle_details['optional_seating'] ?></span>
</button>
<button type="button" class="relative inline-flex items-center w-full px-4 py-2 text-sm font-medium border-b border-gray-200 hover:bg-gray-100 hover:text-black-700 dark:border-gray-600 dark:hover:bg-gray-600 dark:hover:text-white text-black flex-row gap-2">
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 63.67" width="20" height="10">
<path d="M7.69,25.88c-8.3-4.22-7.35-8.92,1-8.43L10.55,21,14.4,9c1.51-4.71,4-9,9-9H66.89c4.24,0,6.94,3.09,8.37,7A9.88,9.88,0,0,1,79,6.69h24.8c3.38,0,6.2.12,9.29,2.32a12.64,12.64,0,0,1,2.66,2.57,32.64,32.64,0,0,1,3.39,6.15l3.2,6.66a2.53,2.53,0,0,1,.25,2.64l-2.52,8.19c-.2.45-.6.64-1.29.5a10.51,10.51,0,0,0-11.6,9c-.06.53,0,1.22-.44,1.54a1.48,1.48,0,0,1-1.09.15H87.15c-.1,2.72-.3,5.83-.52,9.46v5a2.81,2.81,0,0,1-2.81,2.8h-12a2.81,2.81,0,0,1-2.81-2.8V58.62H18.18v2.25a2.82,2.82,0,0,1-2.81,2.8H3.4a2.81,2.81,0,0,1-2.8-2.8V54.4a3,3,0,0,1,0-.43c-.91-11.62-2.19-22.1,7.06-28.09ZM86.78,37.44H85.31a.84.84,0,0,0-.85,1,1.07,1.07,0,0,0,1,1h19.89a.83.83,0,0,0,.84-1h0a1.07,1.07,0,0,0-1-1Zm-2.32,1ZM79.27,11h27.56a7.4,7.4,0,0,1,4.78,3.28l5.5,11.26.11,1.07H83.3L79.27,11Zm32.41,19.19h5.37a1.07,1.07,0,0,1,1,1h0a.83.83,0,0,1-.84.95h-5.37a1.07,1.07,0,0,1-1-.95h0a.83.83,0,0,1,.84-1ZM23,39.66,12.34,38.32C9.83,38,9.16,39.1,10,41.26l1.15,2.79a4.06,4.06,0,0,0,1.44,1.61,4.82,4.82,0,0,0,2.38.65l9.48.08c2.29,0,3.28-.92,2.56-3A5.11,5.11,0,0,0,23,39.66Zm41.27,0,10.63-1.34c2.51-.28,3.19.78,2.33,2.94l-1.15,2.79a4.06,4.06,0,0,1-1.44,1.61,4.82,4.82,0,0,1-2.38.65l-9.48.08c-2.29,0-3.28-.92-2.56-3a5.13,5.13,0,0,1,4-3.71ZM14.54,23.59h60l-2.9-12c-.79-3.66-3.08-6.84-6.84-6.84H25c-3.76,0-5.69,3.26-6.85,6.84l-3.61,12v0Z" style="fill: #000;"></path>
</svg>
<span class="label">Doors</span>
<span class="value"><?php echo $vehicle_details['doors'] ?></span>
</button>
<button type="button" class="relative inline-flex items-center w-full px-4 py-2 text-sm font-medium border-b border-gray-200 hover:bg-gray-100 hover:text-black-700 dark:border-gray-600 dark:hover:bg-gray-600 dark:hover:text-white text-black flex-row gap-2">
<i class="fas fa-gas-pump w-3 h-3 me-3"></i>
<span class="label">Fuel Type</span>
<span class="value"><?php echo $vehicle_details['fuel_type'] ?></span>
</button>
<button type="button" class="relative inline-flex items-center w-full px-4 py-2 text-sm font-medium border-b border-gray-200 hover:bg-gray-100 hover:text-black-700 dark:border-gray-600 dark:hover:bg-gray-600 dark:hover:text-white text-black flex-row gap-2">
<i class="fas fa-clipboard-check w-3 h-3 me-3"></i>
<span class="label">Condition</span>
<span class="value">Used</span>
</button>
<button type="button" class="relative inline-flex items-center w-full px-4 py-2 text-sm font-medium border-b border-gray-200 hover:bg-gray-100 hover:text-black-700 dark:border-gray-600 dark:hover:bg-gray-600 dark:hover:text-white text-black flex-row gap-2">
<i class="fas fa-palette w-3 h-3 me-3"></i>
<span class="label">Interior Colour</span>
<span class="value"><?php echo $vehicle_details['interior_color'] ?></span>
</button>
</div>
</div>
</div>
<!-- Description -->
<div class="m-4">
<!-- <span class="font-bold text-gray-700 dark:text-gray-300">Standard equipment</span> -->
<div id="accordion-equipment" data-accordion="collapse" data-active-classes="bg-white dark:bg-gray-900 text-gray-900 dark:text-white mt-4 transition-all duration-300" data-inactive-classes="text-gray-500 dark:text-gray-400">
<h2 id="accordion-features-heading-1" class="py-2 my-0">
<button type="button" class="flex items-center justify-between w-full p-5 font-medium rtl:text-right text-gray-500 border-b border-gray-200 gap-3 focus:bg-gray-700 rounded-lg dark:bg-gray-700 dark:border-gray-600 dark:text-white bg-white round-lg focus:text-white" data-accordion-target="#accordion-features-body-1" aria-expanded="true" aria-controls="accordion-features-body-1">
<span>Description</span>
<svg data-accordion-icon class="w-3 h-3 rotate-0 shrink-0" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5 5 1 1 5" />
</svg>
</button>
</h2>
<div id="accordion-features-body-1" class="text-gray-900 border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600 dark:text-white transition-all duration-300" aria-labelledby="accordion-features-heading-1">
<div class="bg-white px-4 py-2 border-b border-gray-200 dark:border-gray-700 rounded-lg ">
<p class="mt-4 text-gray-600 dark:text-gray-300 text-sm mb-4">
<?php echo implode("<br><br>", explode(' ', $vehicle_details['comments'])); ?>
</p>
</div>
</div>
</div>
</div>
<!-- Options -->
<div class="m-4">
<p class="mb-4 font-bold text-gray-700 dark:text-gray-300">Options</p>
<div id="accordion-flush" data-accordion="collapse" data-active-classes="bg-white dark:bg-gray-900 text-gray-900 dark:text-white mt-4 transition-all duration-300" data-inactive-classes="text-gray-500 dark:text-gray-400">
<div class="md:grid grid-cols-3">
<ul class="col-span-1 flex-column space-y-4 text-sm font-medium text-gray-500 dark:text-gray-400 md:me-4 mb-4 md:mb-0">
<?php foreach ($groupedFeatures as $category => $features) : ?>
<?php
// Remove special characters and spaces from category for use in HTML IDs
$categorySlug = strtolower(preg_replace('/[^a-z0-9]/', '', $category));
?>
<button class="inline-flex items-center text-left px-4 py-3 rounded-lg hover:text-gray-900 bg-gray-50 hover:bg-gray-300 w-full dark:hover:bg-gray-700 dark:hover:text-white text-gray-900 border border-gray-200 dark:bg-gray-700 dark:border-gray-600 dark:text-white transition-all duration-300 ripple-effect" data-tab-target="#tab-<?= $categorySlug ?>-body-1" data-te-ripple-init>
<?= $category ?>
</button>
<?php endforeach; ?>
</ul>
<div class="sticky-container col-span-2 p-6 bg-gray-50 text-medium text-gray-900 border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600 dark:text-white transition-all duration-300 top-32 lg:top-56" id="tabs-viewer">
</div>
<?php foreach ($groupedFeatures as $category => $features) : ?>
<?php
// Remove special characters and spaces from category for use in HTML IDs
$categorySlug = strtolower(preg_replace('/[^a-z0-9]/', '', $category));
?>
<div class="p-6 bg-gray-50 text-medium text-gray-500 dark:text-gray-400 dark:bg-gray-800 rounded-lg w-full tab-content hidden" id="tab-<?= $categorySlug ?>-body-1">
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-2"><?= $category ?></h3>
<?php foreach ($features as $feature) : ?>
<p class="mb-2 text-gray-500 dark:text-gray-400"><?= $feature['name'] . (isset($feature['value']) && !empty($feature['value']) ? ': <span>' . $feature['value'] . '</span>' : '') ?> </p>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
</div>
<div class="p-4 text-sm text-gray-800 rounded-lg bg-gray-50 dark:bg-gray-800 dark:text-gray-300 mt-4" role="alert">
<span class="font-medium">*Standard Equipment</span> is the default equipment supplied for the Make and Model of this vehicle, but may not represent the final vehicle with additional / altered equipment options.
</div>
</div>
</div>
</div>
<!-- Vehicle Financing and Booking Widget -->
<div class="px-4 col-span-1 relative">
<div class="sticky-container grid gap-3 top-56">
<button role="button" class="prev px-2 py-2 rounded-full bg-neutral-100 text-neutral-900 group absolute top-1/4 left-4 transform -translate-y-1/2 z-10" aria-label="prev" onclick="navigateSlider(-1)">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5 group-active:-translate-x-2 transition-all duration-200 ease-linear">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
</svg>
</button>
<div id="featuredImageContainer" class="ripple-effect" style="width: 100%;" onclick="openFeaturedImage()">
<img id="featuredImage" class="cursor-pointer h-auto max-w-full rounded-lg " src="#" alt="">
</div>
<div id="thumbnailContainer" class="no-scrollbar slides overflow-scroll smooth-scroll relative w-full whitespace-nowrap touch-pan-x before:shrink-0 after:shrink-0 before:w-[36vw] after:w-[36vw] snap-mandatory flex snap-x gap-2 border border-transparent rounded-lg dark:bg-gray-700 dark:border-transparent dark:text-white w-full">
<?php
// Display thumbnail images
foreach ($vehicle_images as $index => $image) {
if (isset($image['url'])) { ?>
<div class='slide flex-shrink-0 w-[70vw] h-[calc(70vw*1.5)] sm:w-[40vw] sm:h-[calc(40vw*1.5)] md:w-[25vw] md:h-[calc(25vw*1.5)] overflow-clip relative snap-center rounded-3xl w-16 h-16'>
<a href="<?php echo esc_url($image['url']) ?>" class="glightbox" data-glightbox="gallery">
<img src="<?php echo esc_url($image['url']) ?>" alt="Image <?php echo $index; ?>" class='block w-full h-full object-cover object-center absolute right-0 animate-parallax h-full w-full object-cover rounded-lg thumbnail' onclick="showFeaturedImage(<?php echo $index; ?>)" />
</a>
</div>
<?php
}
}
?>
</div>
<button role="button" class="next px-2 py-2 rounded-full bg-neutral-100 text-neutral-900 group absolute top-1/4 right-4 transform -translate-y-1/2 z-10" aria-label="next" onclick="navigateSlider(1)">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5 group-active:translate-x-2 transition-all duration-200 ease-linear">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
</svg>
</button>
<div class="flex items-center flex-wrap">
<div class="flex justify-between items-end w-full gap-4 relative whitespace-nowrap snap-x">
<div class="flex justify-between items-end w-full gap-4">
<div class="flex-1 text-gray-900 bg-white border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600 dark:text-white w-full">
<div class="p-4">
<span class="text-xl font-semibold">
<span class="text-gray-600 dark:text-gray-300">
<?php echo $formattedPrice = '$ ' . number_format($vehicle_details['advertise_price']); ?>
</span>
</span>
<p class="text-xs text-gray-400">+ taxes and fees</p>
</div>
</div>
<div class="flex-1 text-gray-900 bg-white border border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600 dark:text-white w-full">
<div class="p-4">
<span class="text-xl font-semibold">
<span class="text-gray-600 dark:text-gray-300 paymentSpan">
<?php echo $formattedPrice = '$ ' . number_format(416); ?>
</span>
<i class="fas fa-calculator cursor-pointer" onclick="openFinanceModal()"></i>
</span>
<p class="text-xs text-gray-400"><span class="rateSpan">7.99</span> for <span class="loanTermSpan">12</span> Months </p>
</div>
</div>
</div>
</div>
</div>
<div class="flex items-center flex-wrap">
<div class="flex items-center justify-between w-full p-5 font-medium rtl:text-right text-gray-500 border-b border-gray-200 rounded-lg dark:bg-gray-700 dark:border-gray-600 dark:text-white bg-white round-lg focus:text-white flex-col gap-3">
<span class="text-red-600 self-start">Estimate Your Payments <i class="fas fa-greater-than text-xs"></i></span>
<!-- Primary Button -->
<button class="w-full bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-gray focus:border-gray-700 ripple-effect" onclick="openAppointmentModal()">
Book a Private Appointment
</button>
<!-- Secondary Button -->
<button class="w-full mt-2 bg-gray-300 hover:bg-gray-400 text-gray-800 font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline-gray focus:border-gray-400 ripple-effect">
Apply for Financing
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Similar Vehicles -->
<div class="flex flex-col md:flex-row max-w-7xl overflow-scroll no-scrollbar">
<div class="mx-auto px-4 mt-10">
<h2 class="text-2xl font-bold mb-4 px-4">Similar Vehicles</h2>
<?php if (!empty($filtered_vehicles)) : ?>
<div class="relative overflow-hidden rounded-lg">
<!-- Left scroll button -->
<button class="absolute left-5 z-10 p-2 bg-neutral-100 rounded-full hover:bg-gray-200 top-1/3" onclick="scrollCarouselLeft()">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5 group-active:-translate-x-2 transition-all duration-200 ease-linear">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5"></path>
</svg>
</button>
<!-- Right scroll button -->
<button class="absolute right-5 z-10 p-2 bg-neutral-100 rounded-full hover:bg-gray-200 top-1/3" onclick="scrollCarouselRight()">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5 group-active:translate-x-2 transition-all duration-200 ease-linear">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5"></path>
</svg>
</button>
<!-- Vehicle cards -->
<div class="flex whitespace-nowrap overflow-x-scroll scroll-smooth px-4 pb-4 flex-stretch space-x-4">
<?php foreach ($filtered_vehicles as $vehicle) : ?>
<div class="flex-shrink-0 w-80">
<div class="bg-white rounded-lg shadow-md hover:shadow-lg transition duration-200 flex flex-col justify-between h-full">
<img src="<?php echo $vehicle['landingImage']; ?>" alt="Vehicle image" class="w-full object-cover rounded-tl-md rounded-tr-md mb-4">
<div class="p-4 ">
<div class="mb-2">
<h3 class="text-lg font-medium text-gray-700 truncate"><?php echo $vehicle['year'] . ' ' . $vehicle['make'] . ' ' . $vehicle['model'] . ' ' . implode(', ', $vehicle['trim']); ?></h3>
<p class="text-sm text-gray-500"><?php echo number_format($vehicle['mileage']) . ' miles - ' . $vehicle['exterior_color'] . ' - ' . $vehicle['transmission_type']; ?></p>
</div>
<div class="flex justify-between items-center">
<span class="text-base font-medium text-gray-700">$<?php echo number_format($vehicle['advertise_price']); ?></span>
<a href="vehicle-details/?vehicle_id=<?php echo $vehicle['id_vehicle']; ?>" class="inline-flex items-center px-3 py-2 text-sm font-medium text-center text-white bg-gray-700 rounded-lg hover:bg-gray-800 hover:text-white">View Details</a>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php else : ?>
<p>No similar vehicles found.</p>
<?php endif; ?>
</div>
</div>
</div>
</div>
<?php
get_footer();
?>
<script>
// modal
const handleFinanceChanges = (financeForm) => {
if (document.querySelectorAll('.paymentSpan').length) {
document.querySelectorAll('.paymentSpan').forEach(el => {
el.textContent = (Number(financeForm.payment) || 0).toLocaleString('en-CA', {
style: 'currency',
currency: 'CAD'
});
})
}
// document.querySelectorAll('.paymentFrequencySpan').forEach(el=>{
// // el.textContent = ${financeForm.frequencyMap[financeForm.paymentFrequency]}
// })
if (document.querySelectorAll('.loanTermSpan').length) {
document.querySelectorAll('.loanTermSpan').forEach(el => {
el.innerHTML = `${financeForm.loanTerm}`;
})
}
if (document.querySelectorAll('.rateSpan').length) {
document.querySelectorAll('.rateSpan').forEach(el => {
el.innerHTML = `${financeForm.intRate}`;
})
}
};
const vehicleDetails = <?php echo json_encode($vehicle_details); ?>;
const appointmentForm = new AppointmentForm();
const financeForm = new FinanceForm(vehicleDetails, handleFinanceChanges);
const modal = new Modal(<?php echo $vehicle_id ?>);
document.body.appendChild(modal.initModal());
function openAppointmentModal() {
modal.toggleModal(appointmentForm);
appointmentForm.initializeFormInputs();
}
function openFinanceModal() {
modal.toggleModal(financeForm);
financeForm.initializeFormInputs();
}
// carousel
let lightbox;
let selectedSlide = 0;
function openFeaturedImage() {
lightbox = GLightbox({
selector: 'glightbox',
touchNavigation: true,
loop: true,
startAt: selectedSlide, // Set the starting slide
onClose: function() {
const slideElements = document.querySelectorAll('#thumbnailContainer .thumbnail');
if (slideElements.length >= lightbox.index) {
const selectedSlideElement = slideElements[lightbox.index];
showFeaturedImage(lightbox.index)
}
}
});
lightbox.open();
}
function showFeaturedImage(idx) {
event.preventDefault();
if (typeof lightbox !== 'undefined') {
lightbox.destroy();
}
setFeatureImage(idx);
}
function setFeatureImage(idx) {
const slideElements = document.querySelectorAll('#thumbnailContainer .thumbnail');
if (slideElements.length >= idx) {
const selectedSlideElement = slideElements[idx];
document.getElementById('featuredImage').src = selectedSlideElement.src;
selectedSlide = idx;
}
}
function navigateSlider(direction) {
selectedSlide += direction;
const slideElements = document.querySelectorAll('#thumbnailContainer .thumbnail');
if (selectedSlide < 0) {
selectedSlide = slideElements.length - 1;
} else if (selectedSlide >= slideElements.length) {
selectedSlide = 0;
}
setFeatureImage(selectedSlide);
}
jQuery(document).ready(function() {
var queryString = window.location.search;
var urlParams = new URLSearchParams(queryString);
var filters = JSON.parse(decodeURIComponent(urlParams.get('filters')));
console.log(filters);
setFeatureImage(0);
const accordionHeaders = document.querySelectorAll('[data-accordion-target]');
accordionHeaders.forEach(header => {
header.addEventListener('click', function() {
const target = document.querySelector(this.getAttribute('data-accordion-target'));
target.classList.toggle('max-h-0');
target.classList.toggle('overflow-hidden');
const expanded = target.classList.contains('max-h-0') ? 'false' : 'true';
this.setAttribute('aria-expanded', expanded);
const icon = this.querySelector('[data-accordion-icon]');
icon.style.transform = expanded === 'true' ? 'rotate(0deg)' : 'rotate(180deg)';
});
});
// ripple effect
const btns = document.getElementsByClassName("ripple-effect");
for (const btn of btns) {
btn.addEventListener("click", rippleEffect);
}
const tabs = document.querySelectorAll('[data-tab-target]');
tabs.forEach(tab => {
tab.addEventListener('click', () => {
const target = document.querySelector(tab.getAttribute('data-tab-target'));
document.querySelector('#tabs-viewer').innerHTML = target.innerHTML;
tabs.forEach(t => {
t.classList.remove('text-white', 'bg-gray-700', 'active');
t.classList.add('bg-gray-50');
});
tab.classList.add('text-white', 'bg-gray-700', 'active');
tab.classList.remove('bg-gray-50');
});
});
});
function rippleEffect(event) {
const btn = event.currentTarget;
// Add classes to the selected element
btn.classList.add("overflow-hidden", "shadow", "relative");
const circle = document.createElement("span");
const rect = btn.getBoundingClientRect();
const diameter = Math.max(btn.clientWidth, btn.clientHeight);
const radius = diameter / 2;
// Calculate the position relative to the button element
const offsetX = event.clientX - rect.left;
const offsetY = event.clientY - rect.top;
circle.style.width = circle.style.height = `${diameter}px`;
circle.style.left = `${offsetX - radius}px`;
circle.style.top = `${offsetY - radius}px`;
circle.classList.add("ripple");
const ripple = btn.getElementsByClassName("ripple")[0];
if (ripple) {
ripple.remove();
}
btn.appendChild(circle);
}
function scrollCarouselLeft() {
const carousel = document.querySelector('.overflow-x-scroll');
carousel.scrollTo({
left: carousel.scrollLeft - carousel.offsetWidth,
behavior: 'smooth'
});
// const carousel = document.querySelector('.overflow-x-scroll');
// carousel.scrollLeft -= carousel.offsetWidth;
}
function scrollCarouselRight() {
const carousel = document.querySelector('.overflow-x-scroll');
const maxScrollRight = carousel.scrollWidth - carousel.offsetWidth; // Calculate max scrollable distance
// Prevent scrolling beyond the end
if (carousel.scrollLeft >= maxScrollRight) {
return;
}
carousel.scrollTo({
left: carousel.scrollLeft + carousel.offsetWidth,
behavior: 'smooth'
});
}
</script>