1import { createHistogramRangeFilter } from '../utils/histogram-helpers.js?v=3'; 2 3 export default (config = {}) => { 4 const params = new URLSearchParams(window.location.search); 5 6 // Currency conversion helpers for URL state handling 7 const currencySymbolToCode = { 8 "â¬": "EUR", 9 "$": "USD", 10 "£": "GBP", 11 }; 12 13 const currencyCodeToSymbol = { 14 "EUR": "â¬", 15 "USD": "$", 16 "GBP": "£", 17 }; 18 19 // Store initial values for comparison 20 const initialMinPrice = config.priceRange?.minValue || 0; 21 const initialMaxPrice = config.priceRange?.maxValue || 2000000; 22 const initialMinLength = config.lengthRange?.minValue || 0; 23 const initialMaxLength = config.lengthRange?.maxValue || 150; 24 25 // Create reactive state for URL synchronization 26 const urlState = Alpine.reactive({ 27 minPrice: initialMinPrice, 28 maxPrice: initialMaxPrice, 29 minLength: initialMinLength, 30 maxLength: initialMaxLength, 31 cabins: config.cabins?.defaultValue || 0, 32 guests: config.guests?.defaultValue || 0, 33 yachtType: config.yachtType?.defaultValue || "any", 34 popularYachtType: null, // Popular yacht type selections 35 selectedYacht: null, // Selected yacht from search (stored as slug) 36 builders: config.builder?.defaultSelected || [], 37 exteriorDesigners: config.exteriorDesigner?.defaultSelected || [], 38 interiorDesigners: config.interiorDesigner?.defaultSelected || [], 39 yearBuilt: config.yearBuilt?.defaultValue || "any", 40 lastRefit: config.lastRefit?.defaultValue || "any", 41 bookingOptions: config.bookingOptions?.defaultSelected || [], 42 features: config.features?.defaultSelected || [], 43 activeFilter: "yachts", 44 gridColumns: 3, 45 currency: "EUR", 46 }); 47 48 const paramMapping = { 49 minPrice: { 50 type: "number", 51 default: () => config.priceRange?.minValue || 0, 52 }, 53 maxPrice: { 54 type: "number", 55 default: () => config.priceRange?.maxValue || 2000000, 56 }, 57 minLength: { 58 type: "number", 59 default: () => config.lengthRange?.minValue || 30, 60 }, 61 maxLength: { 62 type: "number", 63 default: () => config.lengthRange?.maxValue || 150, 64 }, 65 cabins: { 66 type: "number", 67 default: () => config.cabins?.defaultValue || 0, 68 }, 69 guests: { 70 type: "number", 71 default: () => config.guests?.defaultValue || 0, 72 }, 73 yachtType: { 74 type: "string", 75 default: () => config.yachtType?.defaultValue || "any", 76 }, 77 popularYachtType: { 78 type: "string", 79 default: () => null, 80 }, 81 selectedYacht: { 82 type: "string", 83 default: () => null, 84 }, 85 builders: { 86 type: "array", 87 default: () => config.builder?.defaultSelected || [], 88 }, 89 exteriorDesigners: { 90 type: "array", 91 default: () => config.exteriorDesigner?.defaultSelected || [], 92 }, 93 interiorDesigners: { 94 type: "array", 95 default: () => config.interiorDesigner?.defaultSelected || [], 96 }, 97 yearBuilt: { 98 type: "string", 99 default: () => config.yearBuilt?.defaultValue || "any", 100 }, 101 lastRefit: { 102 type: "string", 103 default: () => config.lastRefit?.defaultValue || "any", 104 }, 105 bookingOptions: { 106 type: "array", 107 default: () => config.bookingOptions?.defaultSelected || [], 108 }, 109 features: { 110 type: "array", 111 default: () => config.features?.defaultSelected || [], 112 }, 113 activeFilter: { 114 type: "string", 115 default: () => "yachts", 116 }, 117 gridColumns: { 118 type: "number", 119 default: () => 3, 120 }, 121 currency: { 122 type: "string", 123 default: () => "EUR", 124 }, 125 }; 126 127 Object.entries(paramMapping).forEach(([key, config]) => { 128 if (params.has(key)) { 129 const value = params.get(key); 130 if (config.type === "number") { 131 urlState[key] = Number(value) || config.default(); 132 } else if (config.type === "array") { 133 urlState[key] = value ? value.split(",") : config.default(); 134 } else { 135 // Special handling for currency: convert old symbol values to codes 136 if (key === "currency") { 137 // If it's already a code (EUR, USD, GBP), use it
138 if (["EUR", "USD", "GBP"].includes(value.toUpperCase())) { 139 urlState[key] = value.toUpperCase(); 140 } else { 141 // Otherwise, try to convert from symbol to code 142 urlState[key] = currencySymbolToCode[value] || config.default(); 143 } 144 } else { 145 urlState[key] = value || config.default(); 146 } 147 } 148 } 149 }); 150 151 // Create price and length filter objects 152 const priceFilter = createHistogramRangeFilter( 153 config.priceRange || {}, 154 "price" 155 ); 156 const lengthFilter = createHistogramRangeFilter( 157 config.lengthRange || {}, 158 "length" 159 ); 160 161 const cabinsFilter = { 162 minValue: config.cabins?.minValue || 0, 163 maxValue: config.cabins?.maxValue || 12, 164 value: urlState.cabins, 165 166 increment() { 167 if (this.value < this.maxValue) { 168 this.value++; 169 } 170 }, 171 172 decrement() { 173 if (this.value > this.minValue) { 174 this.value--; 175 } 176 }, 177 178 updateValue(newValue) { 179 const parsed = parseInt(newValue) || 0; 180 this.value = Math.max(this.minValue, Math.min(this.maxValue, parsed)); 181 }, 182 }; 183 184 const guestsFilter = { 185 minValue: config.guests?.minValue || 0, 186 maxValue: config.guests?.maxValue || 24, 187 value: urlState.guests, 188 189 increment() { 190 if (this.value < this.maxValue) { 191 this.value++; 192 } 193 }, 194 195 decrement() { 196 if (this.value > this.minValue) { 197 this.value--; 198 } 199 }, 200 201 updateValue(newValue) { 202 const parsed = parseInt(newValue) || 0; 203 this.value = Math.max(this.minValue, Math.min(this.maxValue, parsed)); 204 }, 205 }; 206 207 const yachtTypeFilter = { 208 options: config.yachtType?.options || [ 209 { value: "any", label: "ANY" }, 210 { value: 1, label: "MOTOR" }, 211 { value: 2, label: "SAILING" }, 212 ], 213 selectedValue: urlState.yachtType, 214 215 updateValue(newValue) { 216 this.selectedValue = newValue; 217 }, 218 }; 219 220 const builderFilter = { 221 builders: config.builder?.builders || [], 222 selectedBuilders: [...(urlState.builders || [])], 223 searchQuery: "", 224 showAll: false, 225 226 get filteredBuilders() { 227 if (!this.searchQuery) { 228 return this.builders; 229 } 230 return this.builders.filter((builder) => 231 builder.name.toLowerCase().includes(this.searchQuery.toLowerCase()) 232 ); 233 }, 234 235 toggleBuilder(builderId) { 236 const index = this.selectedBuilders.indexOf(builderId); 237 if (index > -1) { 238 this.selectedBuilders.splice(index, 1); 239 } else { 240 this.selectedBuilders.push(builderId); 241 } 242 // Force reactivity update 243 this.selectedBuilders = [...this.selectedBuilders]; 244 }, 245 246 updateValue(newBuilders) { 247 this.selectedBuilders = [...newBuilders]; 248 }, 249 }; 250 251 const exteriorDesignerFilter = { 252 exteriorDesigners: config.exteriorDesigner?.exteriorDesigners || [], 253 selectedExteriorDesigners: [...(urlState.exteriorDesigners || [])], 254 searchQuery: "", 255 showAll: false, 256 257 get filteredExteriorDesigners() { 258 if (!this.searchQuery) { 259 return this.exteriorDesigners; 260 } 261 return this.exteriorDesigners.filter((exteriorDesigner) => 262 exteriorDesigner.name.toLowerCase().includes(this.searchQuery.toLowerCase()) 263 ); 264 }, 265 266 toggleExteriorDesigner(exteriorDesignerId) { 267 const index = this.selectedExteriorDesigners.indexOf(exteriorDesignerId); 268 if (index > -1) { 269 this.selectedExteriorDesigners.splice(index, 1); 270 } else { 271 this.selectedExteriorDesigners.push(exteriorDesignerId); 272 } 273 // Force reactivity update 274 this.selectedExteriorDesigners = [...this.selectedExteriorDesigners]; 275 }, 276 277 updateValue(newExteriorDesigners) { 278 this.selectedExteriorDesigners = [...newExteriorDesigners]; 279 }, 280 }; 281 282 const interiorDesignerFilter = { 283 interiorDesigners: config.interiorDesigner?.interiorDesigners || [], 284 selectedInteriorDesigners: [...(urlState.interiorDesigners || [])], 285 searchQuery: "", 286 showAll: false, 287 288 get filteredInteriorDesigners() { 289 if (!this.searchQuery) { 290 return this.interiorDesigners; 291 } 292 return this.interiorDesigners.filter((interiorDesigner) => 293 interiorDesigner.name.toLowerCase().includes(this.searchQuery.toLowerCase()) 294 ); 295 }, 296 297 toggleInteriorDesigner(interiorDesignerId) { 298 const index = this.selectedInteriorDesigners.indexOf(interiorDesignerId); 299 if (index > -1) { 300 this.selectedInteriorDesigners.splice(index, 1); 301 } else { 302 this.selectedInteriorDesigners.push(interiorDesignerId); 303 } 304 // Force reactivity update 305 this.selectedInteriorDesigners = [...this.selectedInteriorDesigners]; 306 }, 307
308 updateValue(newInteriorDesigners) { 309 this.selectedInteriorDesigners = [...newInteriorDesigners]; 310 }, 311 }; 312 313 const yearBuiltFilter = { 314 options: config.yearBuilt?.options || [ 315 { value: "any", label: "ANY" }, 316 { value: "up-to-2-years", label: "UP TO 2 YEARS" }, 317 { value: "up-to-5-years", label: "UP TO 5 YEARS" }, 318 { value: "up-to-10-years", label: "UP TO 10 YEARS" }, 319 { value: "over-10-years", label: "OVER 10 YEARS" }, 320 ], 321 selectedValue: urlState.yearBuilt, 322 323 updateValue(newValue) { 324 this.selectedValue = newValue; 325 }, 326 }; 327 328 const lastRefitFilter = { 329 options: config.lastRefit?.options || [ 330 { value: "any", label: "ANY" }, 331 { value: "up-to-2-years", label: "UP TO 2 YEARS" }, 332 { value: "up-to-5-years", label: "UP TO 5 YEARS" }, 333 { value: "up-to-10-years", label: "UP TO 10 YEARS" }, 334 { value: "over-10-years", label: "OVER 10 YEARS" }, 335 ], 336 selectedValue: urlState.lastRefit, 337 338 updateValue(newValue) { 339 this.selectedValue = newValue; 340 }, 341 }; 342 343 const bookingOptionsFilter = { 344 options: config.bookingOptions?.options || [ 345 { id: "special-offers", label: "Special offers", enabled: false }, 346 ], 347 selectedOptions: [...(urlState.bookingOptions || [])], 348 349 toggleOption(optionId) { 350 const index = this.selectedOptions.indexOf(optionId); 351 if (index > -1) { 352 this.selectedOptions.splice(index, 1); 353 } else { 354 this.selectedOptions.push(optionId); 355 } 356 // Force reactivity update 357 this.selectedOptions = [...this.selectedOptions]; 358 }, 359 360 updateValue(newOptions) { 361 this.selectedOptions = [...newOptions]; 362 }, 363 }; 364 365 const featuresFilter = { 366 options: config.features?.options || [], 367 selectedFeatures: [...(urlState.features || [])], 368 369 toggleFeature(featureId) { 370 const index = this.selectedFeatures.indexOf(featureId); 371 if (index > -1) { 372 this.selectedFeatures.splice(index, 1); 373 } else { 374 this.selectedFeatures.push(featureId); 375 } 376 // Force reactivity update 377 this.selectedFeatures = [...this.selectedFeatures]; 378 }, 379 380 updateValue(newFeatures) { 381 this.selectedFeatures = [...newFeatures]; 382 }, 383 }; 384 385 // Set initial values from URL state 386 priceFilter.minValue = urlState.minPrice; 387 priceFilter.maxValue = urlState.maxPrice; 388 priceFilter.displayCurrency = currencyCodeToSymbol[urlState.currency] || "â¬"; 389 // Initialize display after setting currency to ensure proper formatting 390 priceFilter.initDisplay(); 391 lengthFilter.minValue = urlState.minLength; 392 lengthFilter.maxValue = urlState.maxLength; 393 lengthFilter.initDisplay(); 394 395 return { 396 gridColumns: urlState.gridColumns, 397 activeFilter: urlState.activeFilter, 398 showFilterModal: false, 399 400 // Yacht types dropdown state 401 showYachtTypesDropdown: false, 402 yachtSearchQuery: "", 403 activeSearchYacht: 0, 404 selectedYacht: null, 405 selectedYachts: [], 406 selectedYachtFromSearchId: null, 407 popularYachtType: urlState.popularYachtType, 408 409 // Size dropdown state 410 showSizeDropdown: false, 411 412 // Price dropdown state 413 showPriceDropdown: false, 414 415 // Destination dropdown state 416 showDestinationDropdown: false, 417 destinationSearchQuery: "", 418 activeSearchDestination: 0, 419 420 // Date dropdown state 421 showDateDropdown: false, 422 dateType: "calendar", // 'calendar' or 'flexible' 423 season: null, // null, 'flexible', 'summer', 'winter' 424 months: [], // array of selected month numbers (1-12) 425 calendarRange: { 426 start: null, 427 end: null, 428 }, 429 430 // Mobile filter states 431 showMobileFilters: false, 432 showMobileYachtDropdown: false, 433 showMobileSizeDropdown: false, 434 showMobilePriceDropdown: false, 435 showMobileSortDropdown: false, 436 mobileActiveAccordion: null, // 'type' | 'size' | 'price' | null 437 438 // Sort state 439 sortBy: 'newest', 440 sortOptions: [ 441 { value: 'newest', label: 'Newest Listings' }, 442 { value: 'priceasc', label: 'Price: Low to High' }, 443 { value: 'pricedesc', label: 'Price: High to Low' }, 444 { value: 'lengthasc', label: 'Length: Low to High' }, 445 { value: 'lengthdesc', label: 'Length: High to Low' }, 446 ], 447
448 // Mock yacht search data 449 yachtSearchData: config.yachtSearchData, 450 451 // Mock destination search data 452 destinationSearchData: [ 453 { 454 id: 1, 455 name: "Mediterranean", 456 description: "EUROPE ⢠FRANCE ⢠ITALY ⢠SPAIN", 457 image: "/src/images/octopus-1.jpg", 458 }, 459 { 460 id: 2, 461 name: "The Caribbean", 462 description: "BAHAMAS ⢠BARBADOS ⢠ST. MARTIN", 463 image: "/src/images/octopus-1.jpg", 464 }, 465 { 466 id: 3, 467 name: "North America", 468 description: "USA ⢠CANADA ⢠ALASKA", 469 image: "/src/images/octopus-1.jpg", 470 }, 471 { 472 id: 4, 473 name: "South Pacific", 474 description: "FIJI ⢠TAHITI ⢠NEW ZEALAND", 475 image: "/src/images/octopus-1.jpg", 476 }, 477 { 478 id: 5, 479 name: "Southeast Asia", 480 description: "THAILAND ⢠INDONESIA ⢠MALAYSIA", 481 image: "/src/images/octopus-1.jpg", 482 }, 483 { 484 id: 6, 485 name: "Indian Ocean", 486 description: "MALDIVES ⢠SEYCHELLES ⢠MAURITIUS", 487 image: "/src/images/octopus-1.jpg", 488 }, 489 { 490 id: 7, 491 name: "Central America", 492 description: "COSTA RICA ⢠PANAMA ⢠BELIZE", 493 image: "/src/images/octopus-1.jpg", 494 }, 495 { 496 id: 8, 497 name: "Middle East", 498 description: "UAE ⢠QATAR ⢠OMAN", 499 image: "/src/images/octopus-1.jpg", 500 }, 501 { 502 id: 9, 503 name: "Northern Europe", 504 description: "NORWAY ⢠SWEDEN ⢠FINLAND", 505 image: "/src/images/octopus-1.jpg", 506 }, 507 ], 508 509 // Accordion state management 510 activeAccordion: null, 511 512 // Yacht count for filtering results 513 yachtCount: '', // This will be updated via AJAX 514 515 priceFilter, 516 lengthFilter, 517 cabinsFilter, 518 guestsFilter, 519 yachtTypeFilter, 520 builderFilter, 521 exteriorDesignerFilter, 522 interiorDesignerFilter, 523 yearBuiltFilter, 524 lastRefitFilter, 525 bookingOptionsFilter, 526 featuresFilter, 527 528 yachtLabel() { 529 // Check if a yacht is selected from search 530 if (this.selectedYachtFromSearch) { 531 return this.selectedYachtFromSearch.name.toUpperCase(); 532 } 533 534 // Check if a popular yacht type is selected 535 if (this.popularYachtType) { 536 const popularTypeLabels = { 537 'new-build': 'NEW BUILD', 538 'under-construction': 'UNDER CONSTRUCTION', 539 'classic': 'CLASSIC YACHTS', 540 'superyachts': 'SUPERYACHTS', 541 'multihull': 'MULTIHULL' 542 }; 543 544 if (popularTypeLabels[this.popularYachtType]) { 545 return popularTypeLabels[this.popularYachtType]; 546 } 547 } 548 549 // Check if a specific yacht is selected 550 if (this.selectedYacht) { 551 return this.selectedYacht.name.toUpperCase(); 552 } 553 554 // Check if multiple yachts are selected 555 if (this.selectedYachts && this.selectedYachts.length > 0) { 556 if (this.selectedYachts.length === 1) { 557 return this.selectedYachts[0].toUpperCase(); 558 } else if (this.selectedYachts.length <= 2) { 559 return this.selectedYachts.map(y => y.toUpperCase()).join(', '); 560 } else { 561 return `${this.selectedYachts.length} YACHTS`; 562 } 563 } 564 565 // Check if yacht type is selected 566 if (this.yachtTypeFilter.selectedValue && this.yachtTypeFilter.selectedValue !== 'any') { 567 const option = this.yachtTypeFilter.options.find(o => o.value === this.yachtTypeFilter.selectedValue); 568 return option ? `${option.label} YACHTS` : 'ALL YACHTS'; 569 } 570 571 // Default 572 return 'ALL YACHTS'; 573 }, 574 575 get sizeLabel() { 576 // Check if size range has been modified from initial defaults 577 if (this.lengthFilter.minValue !== initialMinLength || 578 this.lengthFilter.maxValue !== initialMaxLength) { 579 const minDisplay = this.lengthFilter.displayUnit === 'ft' 580 ? Math.round(this.lengthFilter.minValue * 3.28084) 581 : this.lengthFilter.minValue; 582 const maxDisplay = this.lengthFilter.displayUnit === 'ft' 583 ? Math.round(this.lengthFilter.maxValue * 3.28084) 584 : this.lengthFilter.maxValue; 585 const unit = this.lengthFilter.displayUnit === 'ft' ? 'FT' : 'M'; 586 return `${minDisplay}-${maxDisplay}${unit}`; 587 } 588 589 // Default 590 return 'ANY SIZE'; 591 }, 592 593 get priceLabel() { 594 // Check if price range has been modified from initial defaults 595 if (this.priceFilter.minValue !== initialMinPrice || 596 this.priceFilter.maxValue !== initialMaxPrice) { 597 const formatPrice = (value) => { 598 let displayValue = value; 599 let symbol = 'â¬'; 600 601 if (this.priceFilter.displayCurrency === '$') { 602 displayValue = Math.round(value * 1.17); 603 symbol = '$'; 604 } else if (this.priceFilter.displayCurrency === '£') { 605 displayValue = Math.round(value * 0.86); 606 symbol = '£'; 607 } 608 609 if (displayValue >= 1000000) { 610 return symbol + (displayValue / 1000000).toFixed(1).replace(/\.0$/, '') + 'M'; 611 } else if (displayValue >= 1000) { 612 return symbol + (displayValue / 1000).toFixed(0) + 'K'; 613 } 614 return symbol + displayValue; 615 }; 616 617 return `${formatPrice(this.priceFilter.minValue)}-${formatPrice(this.priceFilter.maxValue)}`; 618 } 619 620 // Default 621 return 'ANY PRICE'; 622 }, 623 624 get activeFilterLabel() { 625 switch (this.activeFilter) { 626 case "yachts": 627 return "All yachts"; 628 case "destination": 629 return "Anywhere"; 630 case "date": 631 return "Any week"; 632 default: 633 return "All yachts"; 634 } 635 }, 636 637 selectYacht(yacht) { 638 this.selectedYacht = yacht; 639 this.selectedYachts = [yacht.name]; 640 641 // Handle popular yacht types (special Type selections) 642 const popularTypes = ['new-build', 'under-construction', 'classic', 'superyachts', 'multihull']; 643 644 if (popularTypes.includes(yacht.id)) {
645 // Set the popular yacht type instead of regular yacht type 646 this.popularYachtType = yacht.id; 647 urlState.popularYachtType = yacht.id; 648 649 // Clear regular yacht type when popular type is selected 650 this.yachtTypeFilter.selectedValue = 'any'; 651 652 //this.yachtResults() 653 } else if (yacht.specs === 'Type' || yacht.specs === 'Seasonal') { 654 // For any popular yacht type selection 655 this.popularYachtType = yacht.id; 656 urlState.popularYachtType = yacht.id; 657 658 // Clear regular yacht type 659 this.yachtTypeFilter.selectedValue = 'any'; 660 } else { 661 // For regular yacht selections, clear popular type 662 this.popularYachtType = null; 663 urlState.popularYachtType = null; 664 } 665 666 // Close the dropdown after selection 667 this.showYachtTypesDropdown = false; 668 this.resetDropdownHeight(); 669 this.resetYachtDropdownState(); 670 }, 671 672 clearYachtSelection() { 673 this.selectedYacht = null; 674 this.selectedYachts = []; 675 this.yachtTypeFilter.selectedValue = 'any'; 676 this.popularYachtType = null; 677 urlState.popularYachtType = null; 678 }, 679 680 // Computed property to check if any dropdown is open 681 get anyDropdownOpen() { 682 return this.showYachtTypesDropdown || this.showSizeDropdown || this.showPriceDropdown; 683 }, 684 685 get filteredYachtSearch() { 686 if (!this.yachtSearchQuery || this.yachtSearchQuery.length < 2) { 687 return []; 688 } 689 return this.yachtSearchData 690 .filter((yacht) => 691 yacht.name.toLowerCase().includes(this.yachtSearchQuery.toLowerCase()) 692 ) 693 .sort((a, b) => a.name.localeCompare(b.name)); 694 }, 695 696 get activeSearchYachtData() { 697 const filtered = this.filteredYachtSearch; 698 return filtered.length > 0 ? filtered[this.activeSearchYacht] : null; 699 }, 700 701 // Returns the selected yacht data if it's from search 702 get selectedYachtFromSearch() { 703 if (this.selectedYachtFromSearchId === null) { 704 return null; 705 } 706 return this.yachtSearchData.find(y => y.id === this.selectedYachtFromSearchId) || null; 707 }, 708 709 get filteredDestinationSearch() { 710 if ( 711 !this.destinationSearchQuery || 712 this.destinationSearchQuery.length < 2 713 ) { 714 return []; 715 } 716 return this.destinationSearchData.filter((destination) => 717 destination.name 718 .toLowerCase() 719 .includes(this.destinationSearchQuery.toLowerCase()) 720 ); 721 }, 722 723 get activeSearchDestinationData() { 724 const filtered = this.filteredDestinationSearch; 725 return filtered.length > 0 726 ? filtered[this.activeSearchDestination] 727 : null; 728 }, 729 730 updateURL() { 731 const newParams = new URLSearchParams(); 732 733 // Only add non-default values to keep URL clean 734 Object.entries(paramMapping).forEach(([key, paramConfig]) => { 735 const currentValue = urlState[key]; 736 const defaultValue = paramConfig.default(); 737 738 // Handle array comparison differently 739 if (paramConfig.type === "array") { 740 if ( 741 Array.isArray(currentValue) && 742 currentValue.length > 0 && 743 JSON.stringify(currentValue) !== JSON.stringify(defaultValue) 744 ) { 745 newParams.set(key, currentValue.join(",")); 746 } 747 } else if (currentValue !== defaultValue) { 748 newParams.set(key, currentValue); 749 } 750 }); 751 752 const url = `${window.location.pathname}${ 753 newParams.toString() ? "?" + newParams.toString() : "" 754 }`; 755 window.history.replaceState(null, "", url); 756 }, 757 758 getURL(){ 759 const newParams = new URLSearchParams(); 760 761 // Only add non-default values to keep URL clean 762 Object.entries(paramMapping).forEach(([key, paramConfig]) => { 763 const currentValue = urlState[key]; 764 const defaultValue = paramConfig.default(); 765 766 // Handle array comparison differently 767 if (paramConfig.type === "array") { 768 if ( 769 Array.isArray(currentValue) && 770 currentValue.length > 0 && 771 JSON.stringify(currentValue) !== JSON.stringify(defaultValue) 772 ) { 773 newParams.set(key, currentValue.join(",")); 774 } 775 } else if (currentValue !== defaultValue) { 776 newParams.set(key, currentValue); 777 } 778 }); 779 780 const url = newParams.toString() ? "?" + newParams.toString() : ""; 781 return url; 782 }, 783 784 init() { 785 const savedColumns = localStorage.getItem("charterGridColumns"); 786 if (savedColumns && !params.has("gridColumns")) { 787 this.gridColumns = parseInt(savedColumns); 788 urlState.gridColumns = this.gridColumns; 789 } 790 791 // Sync with global currency store on init 792 const currencyStore = Alpine.store("currency"); 793 if (currencyStore && currencyStore.selected) { 794 this.priceFilter.syncWithCurrencyStore(currencyStore.selected); 795 } 796 797 // Watch for global currency store changes 798 this.$watch("$store.currency.selected", (newCurrency) => { 799 this.priceFilter.syncWithCurrencyStore(newCurrency); 800 }); 801 802 803 // Initialize popularYachtType from URL if present 804 if (urlState.popularYachtType) { 805 this.popularYachtType = urlState.popularYachtType; 806 807 // Also set the selectedYacht to match the popular type 808 const popularTypeNames = { 809 'new-build': 'New Build', 810 'under-construction': 'Under Construction', 811 'classic': 'Classic Yachts', 812 'superyachts': 'Superyachts', 813 'multihull': 'Multihull' 814 }; 815 816 if (popularTypeNames[urlState.popularYachtType]) { 817 this.selectedYacht = { 818 name: popularTypeNames[urlState.popularYachtType], 819 id: urlState.popularYachtType, 820 specs: urlState.popularYachtType.includes('2025') ? 'Seasonal' : 'Type' 821 }; 822 this.selectedYachts = [popularTypeNames[urlState.popularYachtType]]; 823 } 824 } 825 826 // Initialize selectedYachtFromSearchId from URL if present 827 if (urlState.selectedYacht) { 828 const yachtSlug = urlState.selectedYacht; 829 const matchingYacht = this.yachtSearchData.find(y => { 830 const slug = y.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
831 return slug === yachtSlug; 832 }); 833 if (matchingYacht) { 834 this.selectedYachtFromSearchId = matchingYacht.id; 835 } 836 } 837 838 // Initialize displays for both filters 839 this.priceFilter.initDisplay(); 840 this.lengthFilter.initDisplay(); 841 842 let yachtDebounceTimeout; 843 // Set up URL synchronization using Alpine.effect 844 Alpine.effect(() => { 845 var first_submit = document.querySelector(".module-input-search .first-submit")?.value; 846 if(first_submit == 1) this.updateURL(); 847 848 var url = this.getURL(); 849 const link_button = document.getElementById("viewYachtsSaleButton"); 850 const link_button_mobile = document.getElementById("viewYachtsSaleButtonMobile"); 851 if(link_button && first_submit == 1) link_button.href = '/buy-a-yacht/yachts-for-sale'+url; 852 if(link_button_mobile && first_submit == 1) link_button_mobile.href = '/buy-a-yacht/yachts-for-sale'+url; 853 854 //Update the yacht counter after x time after the input is changed 855 clearTimeout(yachtDebounceTimeout); 856 yachtDebounceTimeout = setTimeout(() => { 857 this.yachtResults(1); 858 this.yachtResults(); 859 }, 500); 860 }); 861 862 863 this.$watch("gridColumns", (value) => { 864 localStorage.setItem("charterGridColumns", value); 865 urlState.gridColumns = value; 866 this.$dispatch("grid-columns-changed", { 867 columns: value, 868 }); 869 }); 870 871 this.$watch("activeFilter", (value) => { 872 urlState.activeFilter = value; 873 }); 874 875 const bottomBar = document.getElementById("bottom-bar"); 876 877 if(bottomBar) { 878 let mm = gsap.matchMedia(); 879 880 mm.add("(min-width: 1024px)", () => { 881 this.$watch([ 882 'showYachtTypesDropdown', 883 'showSizeDropdown', 884 'showPriceDropdown', 885 ].join(','), value => { 886 if(this.showYachtTypesDropdown || this.showSizeDropdown || this.showPriceDropdown) { 887 gsap 888 .to( 889 "#bottom-bar", 890 { 891 width: "100%", 892 duration: 0.6, 893 ease: "power2.inOut", 894 } 895 ) 896 } 897 }); 898 }); 899 } 900 901 // Watch price filter changes 902 this.$watch("priceFilter.minValue", () => { 903 urlState.minPrice = this.priceFilter.minValue; 904 if (!document.activeElement || document.activeElement.type !== "text") { 905 this.priceFilter.minDisplay = this.priceFilter.formattedMin; 906 } 907 this.$dispatch("price-range-changed", { 908 min: this.priceFilter.minValue, 909 max: this.priceFilter.maxValue, 910 }); 911 }); 912 913 this.$watch("priceFilter.maxValue", () => { 914 urlState.maxPrice = this.priceFilter.maxValue; 915 if (!document.activeElement || document.activeElement.type !== "text") { 916 this.priceFilter.maxDisplay = this.priceFilter.formattedMax; 917 } 918 this.$dispatch("price-range-changed", { 919 min: this.priceFilter.minValue, 920 max: this.priceFilter.maxValue, 921 }); 922 }); 923 924 // Watch length filter changes 925 this.$watch("lengthFilter.minValue", () => { 926 urlState.minLength = this.lengthFilter.minValue; 927 if (!document.activeElement || document.activeElement.type !== "text") { 928 this.lengthFilter.minDisplay = this.lengthFilter.formattedMin; 929 } 930 this.$dispatch("length-range-changed", { 931 min: this.lengthFilter.minValue, 932 max: this.lengthFilter.maxValue, 933 }); 934 }); 935 936 this.$watch("lengthFilter.maxValue", () => { 937 urlState.maxLength = this.lengthFilter.maxValue; 938 if (!document.activeElement || document.activeElement.type !== "text") { 939 this.lengthFilter.maxDisplay = this.lengthFilter.formattedMax; 940 } 941 this.$dispatch("length-range-changed", { 942 min: this.lengthFilter.minValue, 943 max: this.lengthFilter.maxValue, 944 }); 945 }); 946 947 // Watch cabins filter changes 948 this.$watch("cabinsFilter.value", () => { 949 urlState.cabins = this.cabinsFilter.value; 950 this.$dispatch("cabins-changed", { 951 value: this.cabinsFilter.value, 952 }); 953 }); 954 955 // Watch guests filter changes 956 this.$watch("guestsFilter.value", () => { 957 urlState.guests = this.guestsFilter.value; 958 this.$dispatch("guests-changed", { 959 value: this.guestsFilter.value, 960 }); 961 }); 962 963 964 // Watch yacht type filter changes 965 this.$watch("yachtTypeFilter.selectedValue", () => { 966 urlState.yachtType = this.yachtTypeFilter.selectedValue; 967 this.$dispatch("yacht-type-changed", { 968 value: this.yachtTypeFilter.selectedValue, 969 }); 970 //this.yachtResults() 971 }); 972 973 // Watch popular yacht type changes 974 this.$watch("popularYachtType", () => { 975 urlState.popularYachtType = this.popularYachtType; 976 this.$dispatch("popular-yacht-type-changed", { 977 value: this.popularYachtType, 978 }); 979 }); 980 981 // Watch builder filter changes 982 this.$watch("builderFilter.selectedBuilders", (newBuilders) => { 983 urlState.builders = [...newBuilders]; 984 this.$dispatch("builders-changed", { 985 value: newBuilders, 986 }); 987 }); 988 989 // Watch exterior designer filter changes 990 this.$watch("exteriorDesignerFilter.selectedExteriorDesigners", (newExteriorDesigners) => { 991 urlState.exteriorDesigners = [...newExteriorDesigners]; 992 this.$dispatch("exterior-designers-changed", { 993 value: newExteriorDesigners, 994 }); 995 }); 996 997 // Watch interior designer filter changes 998 this.$watch("interiorDesignerFilter.selectedInteriorDesigners", (newInteriorDesigners) => { 999 urlState.interiorDesigners = [...newInteriorDesigners]; 1000 this.$dispatch("interior-designers-changed", { 1001 value: newInteriorDesigners, 1002 }); 1003 }); 1004 1005 // Watch year built filter changes 1006 this.$watch("yearBuiltFilter.selectedValue", () => { 1007 urlState.yearBuilt = this.yearBuiltFilter.selectedValue; 1008 this.$dispatch("year-built-changed", { 1009 value: this.yearBuiltFilter.selectedValue, 1010 }); 1011 }); 1012 1013 // Watch last refit filter changes 1014 this.$watch("lastRefitFilter.selectedValue", () => { 1015 urlState.lastRefit = this.lastRefitFilter.selectedValue; 1016 this.$dispatch("last-refit-changed", { 1017 value: this.lastRefitFilter.selectedValue, 1018 }); 1019 }); 1020 1021 // Watch booking options filter changes 1022 this.$watch("bookingOptionsFilter.selectedOptions", (newOptions) => { 1023 urlState.bookingOptions = [...newOptions]; 1024 this.$dispatch("booking-options-changed", { 1025 value: newOptions, 1026 }); 1027 }); 1028 1029 // Watch features filter changes 1030 this.$watch("featuresFilter.selectedFeatures", (newFeatures) => { 1031 urlState.features = [...newFeatures]; 1032 this.$dispatch("features-changed", { 1033 value: newFeatures, 1034 }); 1035 }); 1036 1037 // Watch dropdown states to control header visibility 1038 const updateHeaderVisibility = () => { 1039 const anyDropdownOpen = this.showYachtTypesDropdown || this.showSizeDropdown || this.showPriceDropdown; 1040 this.$dispatch('hide-nav', anyDropdownOpen); 1041 }; 1042 this.$watch("showYachtTypesDropdown", updateHeaderVisibility); 1043 this.$watch("showSizeDropdown", updateHeaderVisibility); 1044 this.$watch("showPriceDropdown", updateHeaderVisibility); 1045 1046 // Watch yacht search query changes 1047 this.$watch("yachtSearchQuery", () => { 1048 this.resetSearchYacht(); 1049 }); 1050 1051 // Watch destination search query changes 1052 this.$watch("destinationSearchQuery", () => { 1053 this.resetSearchDestination(); 1054 }); 1055 1056 // Watch filter changes to update yacht count 1057 this.$watch("yachtTypeFilter.selectedValue", () => this.fetchYachtCount()); 1058 this.$watch("lengthFilter.minValue", () => this.fetchYachtCount()); 1059 this.$watch("lengthFilter.maxValue", () => this.fetchYachtCount()); 1060 this.$watch("priceFilter.minValue", () => this.fetchYachtCount()); 1061 this.$watch("priceFilter.maxValue", () => this.fetchYachtCount()); 1062 }, 1063 1064 handleYachtFilter() { 1065 this.activeFilter = "yachts"; 1066 }, 1067 1068 setSeason(val) { 1069 if (this.season === val) { 1070 this.season = null; 1071 } else { 1072 this.season = val; 1073 } 1074 this.months = []; 1075 }, 1076 1077 toggleMonth(val) { 1078 if (!this.months.includes(val)) { 1079 this.season = null; 1080 } 1081 if (this.months.includes(val)) { 1082 this.months = this.months.filter((m) => m !== val); 1083 } else { 1084 this.months.push(val); 1085 } 1086 }, 1087 1088 resetDateDropdownState() { 1089 // Don't reset the actual selections, just close the dropdown 1090 this.showDateDropdown = false;
1091 }, 1092 1093 toggleYachtTypesDropdown() { 1094 if (this.showYachtTypesDropdown) { 1095 // If yacht dropdown is open, close it 1096 this.showYachtTypesDropdown = false; 1097 this.resetDropdownHeight(); 1098 this.resetYachtDropdownState(); 1099 } else { 1100 // If yacht dropdown is closed, open it 1101 if (this.showSizeDropdown || this.showPriceDropdown) { 1102 // If other dropdown is open, close it first, then open this 1103 this.showSizeDropdown = false; 1104 this.resetSizeDropdownHeight(); 1105 this.resetSizeDropdownState(); 1106 1107 this.showPriceDropdown = false; 1108 this.resetPriceDropdownHeight(); 1109 this.resetPriceDropdownState(); 1110 1111 // Wait for other dropdown to close before opening this 1112 setTimeout(() => { 1113 this.showYachtTypesDropdown = true; 1114 this.$nextTick(() => { 1115 this.setDropdownFixedHeight(); 1116 }); 1117 }, 150); // Match the transition duration 1118 } else { 1119 // No other dropdowns open, open immediately 1120 this.showYachtTypesDropdown = true; 1121 this.$nextTick(() => { 1122 this.setDropdownFixedHeight(); 1123 }); 1124 } 1125 1126 // Always close filter modal and date dropdown 1127 this.showFilterModal = false; 1128 this.showDateDropdown = false; 1129 } 1130 }, 1131 1132 handleSizeDropdown() { 1133 if (this.showSizeDropdown) { 1134 // If sizes dropdown is open, close it 1135 this.showSizeDropdown = false; 1136 this.resetSizeDropdownHeight(); 1137 this.resetSizeDropdownState(); 1138 } else { 1139 // If yacht dropdown is closed, open it 1140 if (this.showYachtTypesDropdown || this.showPriceDropdown) { 1141 // If other dropdown is open, close it first, then open this 1142 this.showYachtTypesDropdown = false; 1143 this.resetDropdownHeight(); 1144 this.resetYachtDropdownState(); 1145 1146 this.showPriceDropdown = false; 1147 this.resetPriceDropdownHeight(); 1148 this.resetPriceDropdownState(); 1149 1150 // Wait for other dropdown to close before opening this 1151 setTimeout(() => { 1152 this.showSizeDropdown = true; 1153 this.$nextTick(() => { 1154 this.setSizeDropdownFixedHeight(); 1155 }); 1156 }, 150); // Match the transition duration 1157 } else { 1158 // No other dropdowns open, open immediately 1159 this.showSizeDropdown = true; 1160 this.$nextTick(() => { 1161 this.setSizeDropdownFixedHeight(); 1162 }); 1163 } 1164 1165 // Always close filter modal and date dropdown 1166 this.showFilterModal = false; 1167 this.showDateDropdown = false; 1168 } 1169 }, 1170 1171 handlePriceDropdown() { 1172 if (this.showPriceDropdown) { 1173 // If prices dropdown is open, close it 1174 this.showPriceDropdown = false; 1175 this.resetPriceDropdownHeight(); 1176 this.resetPriceDropdownState(); 1177 } else { 1178 // If yacht dropdown is closed, open it 1179 if (this.showYachtTypesDropdown || this.showSizeDropdown) { 1180 // If other dropdown is open, close it first, then open this 1181 this.showYachtTypesDropdown = false; 1182 this.resetDropdownHeight(); 1183 this.resetYachtDropdownState(); 1184 1185 this.showSizeDropdown = false; 1186 this.resetSizeDropdownHeight(); 1187 this.resetSizeDropdownState(); 1188 1189 // Wait for other dropdown to close before opening this 1190 setTimeout(() => { 1191 this.showPriceDropdown = true; 1192 this.$nextTick(() => { 1193 this.setPriceDropdownFixedHeight(); 1194 }); 1195 }, 150); // Match the transition duration 1196 } else { 1197 // No other dropdowns open, open immediately 1198 this.showPriceDropdown = true; 1199 this.$nextTick(() => { 1200 this.setPriceDropdownFixedHeight(); 1201 }); 1202 } 1203 1204 // Always close filter modal and date dropdown
1205 this.showFilterModal = false; 1206 this.showDateDropdown = false; 1207 } 1208 }, 1209 1210 setDropdownFixedHeight() { 1211 const dropdown = document.querySelector("[data-yacht-dropdown]"); 1212 if (dropdown) { 1213 // Capture the current height (default state) 1214 const currentHeight = dropdown.offsetHeight; 1215 dropdown.style.height = `${currentHeight}px`; 1216 dropdown.style.minHeight = `${currentHeight}px`; 1217 dropdown.style.maxHeight = `${currentHeight}px`; 1218 } 1219 }, 1220 1221 resetDropdownHeight() { 1222 const dropdown = document.querySelector("[data-yacht-dropdown]"); 1223 if (dropdown) { 1224 dropdown.style.height = "auto"; 1225 dropdown.style.minHeight = "auto"; 1226 dropdown.style.maxHeight = "auto"; 1227 } 1228 }, 1229 1230 setSizeDropdownFixedHeight() { 1231 const dropdown = document.querySelector("[data-size-dropdown]"); 1232 if (dropdown) { 1233 // Capture the current height (default state) 1234 const currentHeight = dropdown.offsetHeight; 1235 dropdown.style.height = `${currentHeight}px`; 1236 dropdown.style.minHeight = `${currentHeight}px`; 1237 dropdown.style.maxHeight = `${currentHeight}px`; 1238 } 1239 }, 1240 1241 resetSizeDropdownHeight() { 1242 const dropdown = document.querySelector("[data-size-dropdown]"); 1243 if (dropdown) { 1244 dropdown.style.height = "auto"; 1245 dropdown.style.minHeight = "auto"; 1246 dropdown.style.maxHeight = "auto"; 1247 } 1248 }, 1249 1250 setPriceDropdownFixedHeight() { 1251 const dropdown = document.querySelector("[data-price-dropdown]"); 1252 if (dropdown) { 1253 // Capture the current height (default state) 1254 const currentHeight = dropdown.offsetHeight; 1255 dropdown.style.height = `${currentHeight}px`; 1256 dropdown.style.minHeight = `${currentHeight}px`; 1257 dropdown.style.maxHeight = `${currentHeight}px`; 1258 } 1259 }, 1260 1261 resetPriceDropdownHeight() { 1262 const dropdown = document.querySelector("[data-price-dropdown]"); 1263 if (dropdown) { 1264 dropdown.style.height = "auto"; 1265 dropdown.style.minHeight = "auto"; 1266 dropdown.style.maxHeight = "auto"; 1267 } 1268 }, 1269 1270 setActiveSearchYacht(index) { 1271 this.activeSearchYacht = index; 1272 }, 1273 1274 resetSearchYacht() { 1275 this.activeSearchYacht = 0; 1276 }, 1277 1278 setActiveSearchDestination(index) { 1279 this.activeSearchDestination = index; 1280 }, 1281 1282 resetSearchDestination() { 1283 this.activeSearchDestination = 0; 1284 }, 1285 1286 resetDestinationDropdownState() { 1287 this.destinationSearchQuery = ""; 1288 this.activeSearchDestination = 0; 1289 }, 1290 1291 resetYachtDropdownState() { 1292 this.yachtSearchQuery = ""; 1293 this.activeSearchYacht = 0; 1294 }, 1295 1296 resetSizeDropdownState() { 1297 }, 1298 1299 resetPriceDropdownState() { 1300 }, 1301 1302 openMobileFilters() { 1303 this.showMobileFilters = true; 1304 this.showYachtTypesDropdown = false; // Close desktop yacht dropdown 1305 this.showDestinationDropdown = false; // Close desktop destination dropdown 1306 1307 // Reset dropdown states when opening mobile filters 1308 this.resetYachtDropdownState(); 1309 this.resetDestinationDropdownState(); 1310 1311 // Hide the navigation header 1312 window.dispatchEvent(new CustomEvent('hide-nav', { detail: true })); 1313 1314 window.locomotiveScroll.stop(); 1315 }, 1316 1317 closeMobileFilters() { 1318 this.showMobileFilters = false; 1319 this.showMobileYachtDropdown = false; 1320 this.showMobileSizeDropdown = false; 1321 this.showMobilePriceDropdown = false; 1322 this.showMobileSortDropdown = false; 1323 this.mobileActiveAccordion = null; 1324 1325 // Show the navigation header 1326 window.dispatchEvent(new CustomEvent('hide-nav', { detail: false })); 1327 1328 window.locomotiveScroll.start(); 1329 }, 1330 1331 toggleMobileYachtDropdown() { 1332 this.showMobileYachtDropdown = !this.showMobileYachtDropdown; 1333 }, 1334 1335 closeMobileYachtDropdown() { 1336 this.showMobileYachtDropdown = false;
1337 }, 1338 1339 toggleMobileSizeDropdown() { 1340 this.showMobileSizeDropdown = !this.showMobileSizeDropdown; 1341 }, 1342 1343 closeMobileSizeDropdown() { 1344 this.showMobileSizeDropdown = false; 1345 }, 1346 1347 toggleMobilePriceDropdown() { 1348 this.showMobilePriceDropdown = !this.showMobilePriceDropdown; 1349 }, 1350 1351 closeMobilePriceDropdown() { 1352 this.showMobilePriceDropdown = false; 1353 }, 1354 1355 toggleMobileSortDropdown() { 1356 this.showMobileSortDropdown = !this.showMobileSortDropdown; 1357 }, 1358 1359 closeMobileSortDropdown() { 1360 this.showMobileSortDropdown = false; 1361 }, 1362 1363 toggleMobileAccordion(section) { 1364 this.mobileActiveAccordion = this.mobileActiveAccordion === section ? null : section; 1365 }, 1366 1367 selectSort(value) { 1368 this.sortBy = value; 1369 this.closeMobileSortDropdown(); 1370 1371 var select_order = document.querySelector('.order-search'); 1372 select_order.value = value; 1373 select_order.dispatchEvent(new Event('change')); 1374 }, 1375 1376 getSortLabel() { 1377 const option = this.sortOptions.find(o => o.value === this.sortBy); 1378 return option ? option.label : 'Newest Listings'; 1379 }, 1380 1381 openFilterModal() { 1382 this.showFilterModal = true; 1383 this.showYachtTypesDropdown = false; // Close yacht types dropdown 1384 this.showSizeDropdown = false; // Close sizes dropdown 1385 this.showDestinationDropdown = false; // Close destination dropdown 1386 this.showDateDropdown = false; // Close date dropdown 1387 1388 // Reset dropdown states when opening filter modal 1389 this.resetYachtDropdownState(); 1390 this.resetSizeDropdownState(); 1391 this.resetDestinationDropdownState(); 1392 1393 window.locomotiveScroll.stop(); 1394 }, 1395 1396 closeFilterModal() { 1397 this.showFilterModal = false; 1398 window.locomotiveScroll.start(); 1399 }, 1400 1401 applyFilters() { 1402 this.yachtResults(); 1403 this.closeFilterModal(); 1404 }, 1405 1406 // Close all dropdowns and reset their states 1407 closeAllDropdowns() { 1408 this.showYachtTypesDropdown = false; 1409 this.showSizeDropdown = false; 1410 this.showPriceDropdown = false; 1411 this.resetDropdownHeight(); 1412 this.resetSizeDropdownHeight(); 1413 this.resetPriceDropdownHeight(); 1414 this.resetYachtDropdownState(); 1415 this.resetSizeDropdownState(); 1416 this.resetPriceDropdownState(); 1417 }, 1418 1419 // Mock fetch function to get yacht count based on current filters 1420 // TODO: Replace with real API endpoint when backend is ready 1421 async fetchYachtCount() { 1422 // Example of real API call (commented out for backend handoff): 1423 // const params = new URLSearchParams({ 1424 // yachtType: this.yachtTypeFilter.selectedValue || 'any', 1425 // minPrice: this.priceFilter.minValue, 1426 // maxPrice: this.priceFilter.maxValue, 1427 // minLength: this.lengthFilter.minValue, 1428 // maxLength: this.lengthFilter.maxValue, 1429 // cabins: this.cabinsFilter.value, 1430 // guests: this.guestsFilter.value, 1431 // }); 1432 // const response = await fetch(`/api/buy/yachts/count?${params}`); 1433 // const data = await response.json(); 1434 // this.yachtCount = data.count; 1435 1436 // Mock implementation - returns count based on filters to simulate backend response 1437 return new Promise((resolve) => { 1438 setTimeout(() => { 1439 const mockCounts = { 1440 'any': 286, 1441 'motor': 180, 1442 'sailing': 106 1443 }; 1444 let count = mockCounts[this.yachtTypeFilter.selectedValue] || 286; 1445 1446 // Simulate size filtering 1447 if (this.lengthFilter.minValue > this.lengthFilter.absoluteMin || 1448 this.lengthFilter.maxValue < this.lengthFilter.absoluteMax) { 1449 count = Math.floor(count * 0.7); // ~70% when size is filtered 1450 } 1451 1452 // Simulate price filtering 1453 if (this.priceFilter.minValue > this.priceFilter.absoluteMin || 1454 this.priceFilter.maxValue < this.priceFilter.absoluteMax) { 1455 count = Math.floor(count * 0.6); // ~60% when price is filtered 1456 } 1457 1458 // this.yachtCount = count; 1459 // resolve(count); 1460 }, 100); // Small delay to simulate network 1461 }); 1462 }, 1463 1464 selectYachtType(type) { 1465 // Clear any popular yacht selection 1466 this.selectedYacht = null; 1467 this.selectedYachts = []; 1468 this.popularYachtType = null; 1469 urlState.popularYachtType = null; 1470 1471 // Clear any yacht from search selection 1472 this.selectedYachtFromSearchId = null; 1473 urlState.selectedYacht = null; 1474 1475 // Update the yacht type filter 1476 this.yachtTypeFilter.updateValue(type); 1477 1478 // Keep dropdown open (don't close on selection) 1479 }, 1480 1481 selectYachtFromSearch(yacht) { 1482 // Store the selected yacht id 1483 this.selectedYachtFromSearchId = yacht.id; 1484 1485 // Generate slug from yacht name and store in URL 1486 const yachtSlug = yacht.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, ''); 1487 urlState.selectedYacht = yachtSlug;
1488 1489 // Clear any popular yacht selection 1490 this.selectedYacht = null; 1491 this.selectedYachts = []; 1492 this.popularYachtType = null; 1493 urlState.popularYachtType = null; 1494 1495 // Reset yacht type filter to "any" since we're selecting a specific yacht 1496 this.yachtTypeFilter.updateValue('any'); 1497 1498 // Clear the search query 1499 this.yachtSearchQuery = ''; 1500 }, 1501 1502 clearSelectedYachtFromSearch() { 1503 this.selectedYachtFromSearchId = null; 1504 urlState.selectedYacht = null; 1505 }, 1506 1507 clearAllFilters() { 1508 // Reset all filters to default values 1509 this.priceFilter.minValue = this.priceFilter.absoluteMin; 1510 this.priceFilter.maxValue = this.priceFilter.absoluteMax; 1511 this.lengthFilter.minValue = this.lengthFilter.absoluteMin; 1512 this.lengthFilter.maxValue = this.lengthFilter.absoluteMax; 1513 this.cabinsFilter.value = this.cabinsFilter.minValue; 1514 this.guestsFilter.value = this.guestsFilter.minValue; 1515 this.yachtTypeFilter.selectedValue = "any"; 1516 this.builderFilter.selectedBuilders = []; 1517 this.exteriorDesignerFilter.selectedExteriorDesigners = []; 1518 this.interiorDesignerFilter.selectedInteriorDesigners = []; 1519 this.yearBuiltFilter.selectedValue = "any"; 1520 this.lastRefitFilter.selectedValue = "any"; 1521 this.bookingOptionsFilter.selectedOptions = []; 1522 this.featuresFilter.selectedFeatures = []; 1523 1524 // Reset yacht selections 1525 this.selectedYacht = null; 1526 this.selectedYachts = []; 1527 1528 // Reset displays 1529 this.priceFilter.initDisplay(); 1530 this.lengthFilter.initDisplay(); 1531 }, 1532 1533 yachtResults(count){ 1534 if(count == 1) var action = '/core/?pages:search_get_data_count'; 1535 else var action = '/core/?pages:search_get_data'; 1536 1537 var y_for = document.querySelector(".module-input-search .yacht_for")?.value; 1538 var promobanner = document.querySelector(".module-input-search .promobanner")?.value; 1539 var order = document.querySelector(".module-input-search .order-search")?.value; 1540 var first_submit = document.querySelector(".module-input-search .first-submit")?.value; 1541 var input_bar = document.querySelector(".module-input-search .input-bar")?.value; 1542 1543 const currencyStore = Alpine.store("currency"); 1544 const formData = new FormData(); 1545 if(this.selectedYachtFromSearch) formData.append('yref', this.selectedYachtFromSearch.id); 1546 formData.append('min_price', this.priceFilter.minValue); 1547 formData.append('max_price', this.priceFilter.maxValue); 1548 formData.append('currency', urlState.currency); 1549 formData.append('min_loa', this.lengthFilter.minValue); 1550 formData.append('max_loa', this.lengthFilter.maxValue); 1551 formData.append('cabins', this.cabinsFilter.value); 1552 formData.append('guest', this.guestsFilter.value); 1553 formData.append('id_type', this.yachtTypeFilter.selectedValue); 1554 formData.append('id_builder', this.builderFilter.selectedBuilders); 1555 formData.append('year_range', this.yearBuiltFilter.selectedValue); 1556 formData.append('refit_range', this.lastRefitFilter.selectedValue); 1557 formData.append('bookingOptions', this.bookingOptionsFilter.selectedOptions); 1558 formData.append('features_list', this.featuresFilter.selectedFeatures); 1559 formData.append('popular_search', this.popularYachtType); 1560 formData.append('exterior_designer', this.exteriorDesignerFilter.selectedExteriorDesigners); 1561 formData.append('interior_designer', this.interiorDesignerFilter.selectedInteriorDesigners); 1562 formData.append('currency', currencyStore.selected); 1563 formData.append('yachtFor', y_for); 1564 formData.append('promobanner', promobanner); 1565 formData.append('order', order); 1566 formData.append('count', 1); 1567 1568 if(first_submit == 1 || input_bar == 1){ 1569 fetch(action, { 1570 method: 'POST', 1571 body: formData 1572 }) 1573 .then(response => response.text()) 1574 .then(data => { 1575 if(count == 1) this.yachtCount = data; 1576 else{ 1577 var grid_container = document.querySelector(".grid-container"); 1578 if(grid_container) grid_container.innerHTML = data; 1579 } 1580 1581 const yacht_number = document.querySelectorAll(".search-yacht-card").length; 1582 var counter_result_container = document.getElementById("countResultYacht"); 1583 if(counter_result_container) counter_result_container.textContent =
1583yacht_number +' results'; 1584 1585 if(input_bar == 1) document.querySelector('.first-submit').value = 1; 1586 }); 1587 } else if(!count) { 1588 document.querySelector('.first-submit').value = 1 1589 } 1590 1591 }, 1592 }; 1593 }; 1594
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.