1import { Controller } from "@hotwired/stimulus" 2import { numeroAR } from "lib/numero_ar" 3 4// Stimulus muy chico: muestra/oculta el bloque de direccion cuando el cliente 5// elige "Envio a domicilio" en el checkout del menu publico, y valida que 6// la coord seleccionada quede dentro del radio configurado por la tienda. 7export default class extends Controller { 8 static targets = ["addressBlock", "rangeWarning", "submitBtn", "cashBlock"] 9 static values = { 10 clubLat: Number, 11 clubLng: Number, 12 maxRadiusKm: Number 13 } 14 15 connect() { 16 // Cuando los hidden lat/lng del autocomplete cambian (por seleccion), 17 // recalculamos la distancia y mostramos warning si excede el radio. 18 const latInput = this.element.querySelector("input[name='delivery[latitude]']") 19 if (latInput) { 20 const observer = new MutationObserver(() => this._checkRadius()) 21 observer.observe(latInput, { attributes: true, attributeFilter: ["value"] }) 22 // Tambien por evento input por si JS setea via valueSetter 23 latInput.addEventListener("input", () => this._checkRadius()) 24 } 25 26 // El "¿con cuánto pagás al repartidor?" solo aplica cuando los productos se 27 // pagan al recibir (modo reserva). En "pago online completo" ya pagó todo 28 // por MP, asi que lo ocultamos. Se re-evalua al cambiar el modo de pago. 29 this.element.closest("form")?.querySelectorAll("input[name=mode]").forEach((r) => { 30 r.addEventListener("change", () => this._syncCashBlock()) 31 }) 32 this._syncCashBlock() 33 } 34 35 _selectedMode() { 36 const form = this.element.closest("form") 37 if (!form) return null 38 const checked = form.querySelector("input[name=mode]:checked") 39 if (checked) return checked.value 40 const hidden = form.querySelector("input[name=mode]") 41 return hidden ? hidden.value : null 42 } 43 44 _syncCashBlock() { 45 if (!this.hasCashBlockTarget) return 46 const isReservation = this._selectedMode() === "reservation" 47 this.cashBlockTarget.classList.toggle("hidden", !isReservation) 48 if (!isReservation) { 49 const input = this.cashBlockTarget.querySelector("input") 50 if (input) input.value = "" // no mandar cash_offered si no aplica 51 } 52 } 53 54 show() { 55 if (this.hasAddressBlockTarget) { 56 this.addressBlockTarget.classList.remove("hidden") 57 const input = this.addressBlockTarget.querySelector("input[type=text]") 58 if (input) { 59 input.focus() 60 setTimeout(() => input.scrollIntoView({ behavior: "smooth", block: "start" }), 150) 61 } 62 } 63 } 64 65 hide() { 66 if (this.hasAddressBlockTarget) { 67 this.addressBlockTarget.classList.add("hidden") 68 } 69 this._hideWarning() 70 } 71 72 _checkRadius() { 73 const radius = this.maxRadiusKmValue 74 if (!radius || radius <= 0) return 75 const lat = numeroAR(this.element.querySelector("input[name='delivery[latitude]']")?.value) 76 const lng = numeroAR(this.element.querySelector("input[name='delivery[longitude]']")?.value) 77 if (isNaN(lat) || isNaN(lng)) { this._hideWarning(); return } 78 const clubLat = this.clubLatValue, clubLng = this.clubLngValue 79 if (!clubLat || !clubLng) return 80 81 const km = this._haversine(clubLat, clubLng, lat, lng) 82 if (km > radius) { 83 this._showWarning(`La dirección está a ${km.toFixed(1)} km de la tienda. El envÃo llega hasta ${radius} km.`) 84 } else { 85 this._hideWarning() 86 } 87 } 88 89 _showWarning(msg) { 90 if (!this.hasRangeWarningTarget) return 91 this.rangeWarningTarget.textContent = "â " + msg 92 this.rangeWarningTarget.classList.remove("hidden") 93 // Form-level: deshabilita el submit hasta que cambie la direccion. 94 const form = this.element.closest("form") 95 const submit = form?.querySelector("input[type=submit]") 96 if (submit) { submit.disabled = true; submit.classList.add("opacity-50", "cursor-not-allowed") } 97 } 98 99 _hideWarning() { 100 if (this.hasRangeWarningTarget) this.rangeWarningTarget.classList.add("hidden") 101 const form = this.element.closest("form") 102 const submit = form?.querySelector("input[type=submit]") 103 if (submit) { submit.disabled = false; submit.classList.remove("opacity-50", "cursor-not-allowed") } 104 } 105 106 _haversine(lat1, lon1, lat2, lon2) { 107 const r = 6371 108 const toRad = d => d * Math.PI / 180 109 const dLat = toRad(lat2 - lat1) 110 const dLon = toRad(lon2 - lon1) 111 const a = Math.sin(dLat / 2) ** 2 + 112 Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * 113 Math.sin(dLon / 2) ** 2 114 return r * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)) 115 } 116}
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.