1import { Controller } from "@hotwired/stimulus" 2 3export default class extends Controller { 4 static targets = ["container", "loading", "error", "form", "submitBtn"] 5 6 async open(event) { 7 event.preventDefault() 8 9 // Find the modal element 10 const modalElement = document.getElementById('bulkPrintModal') 11 if (!modalElement) { 12 console.error("Bulk print modal not found") 13 return 14 } 15 16 this.resetUi(modalElement) 17 18 const bulkSelectController = this.application.getControllerForElementAndIdentifier( 19 document.querySelector('[data-controller~="bulk-select"]'), 20 "bulk-select" 21 ) 22 23 const ids = bulkSelectController?.selectedIds || [] 24 25 if (!ids.length) { 26 alert("Please select at least one order") 27 return 28 } 29 30 // Show modal (Bootstrap 3) 31 window.jQuery(modalElement).modal("show") 32 33 this.loading(modalElement).style.display = "" 34 this.form(modalElement).style.display = "none" 35 this.submitBtn(modalElement).disabled = true 36 37 try { 38 const url = this.data.get("ratesUrl") 39 const data = await this.postJSON(url, { ids }) 40 this.renderRates(modalElement, data) 41 } catch(err) { 42 this.showError(modalElement, err.message || "Failed to fetch rates") 43 } finally { 44 this.loading(modalElement).style.display = "none" 45 } 46 } 47 48 resetUi(modal) { 49 this.container(modal).innerHTML = "" 50 const error = this.error(modal) 51 error.style.display = "none" 52 error.textContent = "" 53 this.loading(modal).style.display = "none" 54 this.form(modal).style.display = "" 55 this.submitBtn(modal).disabled = false 56 } 57 58 showError(modal, message) { 59 const error = this.error(modal) 60 error.textContent = message 61 error.style.display = "" 62 } 63 64 renderRates(modal, data) { 65 const container = this.container(modal) 66 container.innerHTML = "" 67 let index = 0 68 69 Object.entries(data).forEach(([id, value]) => { 70 if (value.error) { 71 const errorDiv = document.createElement("div") 72 errorDiv.className = "alert alert-warning" 73 errorDiv.innerHTML = `<strong>Order #${id}:</strong> ${value.error}` 74 container.appendChild(errorDiv) 75 return 76 } 77 78 const rates = Array.isArray(value.rates) ? value.rates : JSON.parse(value.rates || "[]") 79 let ratesOptions = ""
80 rates.forEach(rate => { 81 const prettyName = rate.ServiceTypePretty || rate.ServiceDescription || `${rate.ServiceType} ${rate.PackageType}` 82 ratesOptions += `<option value='${JSON.stringify(rate)}'>${prettyName} - $${rate.Amount} - ${rate.DeliverDays} days</option>` 83 }) 84 85 const itemDiv = document.createElement("div") 86 itemDiv.className = "well well-sm" 87 itemDiv.innerHTML = ` 88 <div style="margin-bottom: 10px;"> 89 <span class='strong'>Order #</span>${id} 90 <span class='strong'>Due Date:</span> ${value.due} 91 <span class='strong'>Quantity:</span> ${value.quantity} 92 <input type='hidden' value='${id}' name='bulk_rate[${index}][id]'> 93 </div> 94 <div> 95 <select name='bulk_rate[${index}][rate]' class="form-control" style="width: 100%;"> 96 ${ratesOptions} 97 </select> 98 </div> 99 ` 100 container.appendChild(itemDiv) 101 index++ 102 }) 103 104 this.form(modal).style.display = "" 105 this.submitBtn(modal).disabled = false 106 } 107 108 async submit(event) { 109 event.preventDefault() 110 const modal = document.getElementById('bulkPrintModal') 111 const submitBtn = this.submitBtn(modal) 112 113 submitBtn.disabled = true 114 submitBtn.innerHTML = '<i class="fa fa-spinner fa-spin"></i> Processing...' 115 116 try { 117 const url = this.data.get("printUrl") 118 const formData = new FormData(this.form(modal)) 119 120 const response = await fetch(url, { 121 method: "POST", 122 headers: { 123 "X-CSRF-Token": document.querySelector('meta[name="csrf-token"]')?.content 124 }, 125 body: formData 126 }) 127 128 const data = await response.json() 129 130 if (data && data.error) { 131 throw new Error(data.error) 132 } else if (data && data.url) { 133 window.open(data.url) 134 window.location.reload() 135 } else { 136 throw new Error("Label generation failed (no URL returned)") 137 } 138 } catch(err) { 139 this.showError(modal, err.message || "Failed to print labels") 140 submitBtn.disabled = false 141 submitBtn.innerHTML = '<i class="fa fa-print"></i> Purchase & Print Selected Labels' 142 } 143 } 144 145 // Helper methods to find targets in the modal since they are outside our controller scope 146 container(modal) { return modal.querySelector('[data-bulk-print-target="container"]') } 147 loading(modal) { return modal.querySelector('[data-bulk-print-target="loading"]') } 148 error(modal) { return modal.querySelector('[data-bulk-print-target="error"]') } 149 form(modal) { return modal.querySelector('[data-bulk-print-target="form"]') } 150 submitBtn(modal) { return modal.querySelector('[data-bulk-print-target="submitBtn"]') } 151 152 async postJSON(url, payload) { 153 const token = document.querySelector('meta[name="csrf-token"]')?.content 154 const resp = await fetch(url, { 155 method: "POST", 156 headers: { 157 "Content-Type": "application/json", 158 "X-CSRF-Token": token 159 }, 160 body: JSON.stringify(payload || {}) 161 }) 162 163 const data = await resp.json().catch(() => ({})) 164 if (!resp.ok) throw new Error(data.error || "Request failed") 165 return data 166 } 167}
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.